Tomcat日志中定位并发问题的步骤与方法
并发问题的定位需依赖Tomcat的多类日志,核心日志包括:
pattern="%h %l %u %t "%r" %s %b %D",其中%D为请求处理时间)。日志级别设置为DEBUG或INFO(避免WARN/ERROR过滤关键信息),并保留足够的历史数据以便回溯。通过日志中的关键词快速定位并发问题的类型:
catalina.out中出现java.util.concurrent.ThreadPoolExecutor$AbortPolicy拒绝任务(如RejectedExecutionException: Task java.util.concurrent.FutureTask@xxxx rejected from java.util.concurrent.ThreadPoolExecutor@xxxx),或maxThreads设置过小导致Waiting for available thread(线程池无可用线程);catalina.out中出现java.lang.Thread.State: BLOCKED(线程被阻塞,如等待锁),或Waiting for monitor entry(等待进入同步块);catalina.out中出现Found one Java-level deadlock(明确提示死锁),或线程转储中多个线程互相等待对方持有的锁;java.util.ConcurrentModificationException(并发修改集合);%D(请求耗时)显著增加,且伴随线程池activeThreads接近maxThreads。通过线程ID将日志与线程状态关联,还原并发执行的流程:
doGet/doPost方法)中,使用Thread.currentThread().setName("prefix-"+request.getRequestURI())为线程设置唯一标识(如/api/user请求的线程命名为api-user-123),避免Tomcat线程池复用导致的日志混淆;jstack工具生成线程转储(命令:jstack -l <pid> > thread_dump.log),分析线程的状态(如RUNNABLE、BLOCKED、WAITING)及锁持有情况(如locked <0x00000000f5d3a7b0>)。例如,若catalina.out中发现某线程长时间BLOCKED,可通过线程转储找到其等待的锁及持有该锁的线程。根据日志特征深入定位问题根源:
server.xml中的Executor配置(如maxThreads是否过小,minSpareThreads是否满足基础并发需求),或是否有线程泄漏(如未关闭的数据库连接、HTTP连接,导致线程无法释放);synchronized块内的方法),检查是否存在不必要的同步(如同步整个方法而非代码块);deadlock信息,找到互相等待的线程及锁(如Thread-1持有锁A等待锁B,Thread-2持有锁B等待锁A),调整锁的获取顺序(如所有线程按lockA→lockB的顺序获取锁);ConcurrentHashMap替代HashMap,AtomicInteger替代int)或读写锁(如ReentrantReadWriteLock)优化并发访问。结合监控工具实时观察并发状态,验证日志分析结果:
jconsole或VisualVM连接Tomcat,查看Catalina:type=ThreadPool,name="http-nio-8080"(连接器线程池)的currentThreadsBusy(当前忙碌线程数)、maxThreads(最大线程数)、queueSize(等待队列大小)等指标,实时监控线程池状态;java.lang.OutOfMemoryError: Java heap space),开启GC日志(参数:-Xloggc:/path/to/gc.log -XX:+PrintGCDetails),分析内存泄漏(如未释放的对象占满堆内存);apr、tomcat-native库),提升Tomcat处理并发连接的能力(如protocol="org.apache.coyote.http11.Http11AprProtocol")。