You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何分析线程转储中导致线程WAITING的原因及应用挂起问题

分析Tomcat线程耗尽导致服务挂起的问题

Hey,我来帮你一步步拆解这个问题——你的场景是典型的线程资源耗尽但CPU/内存无异常,核心是大量线程卡在WAITING状态没释放,先给你梳理分析线程转储的实操方法,再针对你的情况给出排查方向:

一、先搞懂你的症状指向什么

线程数打满但CPU仅10%、内存无变化,说明不是CPU密集计算或内存泄漏,而是线程都卡在某个等待资源的环节——比如等数据库连接、等外部服务响应,或者锁等待(不过WAITING一般是等待通知,不是阻塞锁)。

二、手把手教你分析线程转储

fastthread.io没检测出异常很正常,它只是做通用检查,还要结合你的业务场景深挖:

1. 先筛选关键线程组

Tomcat的线程主要分两类,先聚焦核心工作线程:

  • http-nio-8080-exec-*(或http-bio/http-apr,看你用的连接器):处理HTTP请求的核心线程,这些线程打满的话,新请求就接不进来
  • 应用自定义线程:你代码里创建的业务线程池线程

把所有http-nio-8080-exec-*这类线程挑出来,看它们的状态和调用栈。

2. 深挖WAITING线程的调用栈

WAITING状态的线程通常是调用了Object.wait()或LockSupport.park(),重点看调用栈的最顶部3-5个方法:

  • 如果看到org.apache.tomcat.dbcp.dbcp2.PoolingDataSource.getConnection这类方法,那大概率是数据库连接池耗尽——所有工作线程都在等数据库连接,没新连接可用就一直挂着
  • 如果是java.util.concurrent.LinkedBlockingQueue.take(),可能是你的业务线程池阻塞在取任务,或者Tomcat的工作队列满了(不过你是线程数到上限,优先看工作线程的调用栈)
  • 如果调用栈里有第三方API的类(比如HttpClient、Redis客户端),那很可能是外部服务响应极慢/超时,线程被挂起没释放
  • 如果是java.lang.Thread.sleep(),那是代码里主动sleep,但一般不会批量出现

3. 对比正常/异常的线程转储

你可以在服务正常时抓一次线程转储,和挂起前的转储对比:

  • 看哪些线程的状态从RUNNABLE变成了WAITING
  • 找所有WAITING线程的调用栈共性——比如都卡在同一个数据库查询、同一个外部API调用

4. 结合日志和监控辅助验证

  • 查Tomcat的catalina.out或localhost.log,有没有Could not get a connection from pool这类连接池报错,或者Connection timed out的外部调用错误
  • 看Datadog里的数据库连接数趋势、外部API调用耗时,是不是在挂起前这些指标突然飙升

三、针对你的场景的具体排查方向

1. 检查Tomcat连接器配置

打开server.xml的Connector节点,核对这几个参数:

  • maxThreads:Tomcat7默认是200,如果你设得太小,高并发下会打满;如果设得合理但还是耗尽,那是线程泄漏
  • connectionTimeout:默认20000ms,如果设置太长,线程会一直挂着等请求处理完
  • acceptCount:等待队列长度,默认100,队列满了新连接会被拒绝,但你的情况是线程数到上限,优先看前两个参数

2. 排查数据库连接池泄漏

如果用了DBCP/C3P0连接池:

  • 检查代码里有没有获取连接后没关闭的情况——比如try块里拿了连接,finally里没写conn.close()
  • 看连接池的maxTotal(最大连接数)是不是太小,比如设置成10,但业务高峰需要20个连接,导致线程都在等
  • 查数据库的连接数监控,是不是Tomcat的连接数一直上涨,直到打满数据库的最大连接数

3. 排查外部依赖的阻塞

如果应用调用了外部API、Redis、MQ等:

  • 检查这些依赖的超时时间设置——是不是没设超时,或者超时时间太长(比如5分钟),导致线程一直挂起等响应
  • 有没有用同步调用且没做超时处理,一旦外部服务变慢,线程就被占住不能释放

4. 检查线程泄漏

有没有代码里创建了线程但没正确关闭:

  • 比如自定义线程池没调用shutdown(),导致线程一直存活
  • 用了ThreadLocal但没在请求结束后清理,导致线程持有对象不能被回收,线程一直存活

四、快速验证的小技巧

  1. 临时调整Tomcat参数:把maxThreads调大到400,看是不是复发时间变长——如果是,说明确实是线程被占住没释放,不是配置太小
  2. 开启连接池监控:在Tomcat里配置连接池的JMX监控,实时看连接的activeCount(活跃连接数)、idleCount(空闲连接数),确认是不是连接池耗尽
  3. 给外部调用加超时:比如用HttpClient的话,设置connectTimeout=5000和socketTimeout=10000,强制超时释放线程

内容的提问来源于stack exchange,提问作者black sensei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:46:05