Spring应用大量TIMED_WAITING线程问题排查求助
Spring Boot应用压测后TIMED_WAITING线程激增及二次压测超时排查方案
现象梳理
- 应用启动初始线程状态:
blocked:0,new:0,runnable:17,terminated:0,time-waiting:21,waiting:12 - 首次执行压测命令:
ab -s 300 -n 100000 -c 50 "http://my-address/v1/my-collection",压测后线程状态变为:blocked:0,new:0,runnable:21,terminated:0,time-waiting:73,waiting:12 - 二次执行相同压测时出现超时,仅完成45339请求
- 线程dump显示
http-nio线程及ThreadPoolTaskScheduler线程处于TIMED_WAITING状态
核心排查方向及步骤
1. 拆解TIMED_WAITING线程的等待原因
TIMED_WAITING本身不一定是异常,但数量激增且伴随压测超时,需结合线程栈具体分析:
http-nio线程:这类线程属于Tomcat NIO线程池,通常因等待新请求或连接进入TIMED_WAITING。查看线程栈中是否存在如下调用链:
java.lang.Thread.State: TIMED_WAITING (parking) at jdk.internal.misc.Unsafe.park(Native Method) at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:252) at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:719)若大量线程停留在
NioEndpoint$Poller.run,说明Tomcat线程池已处理完当前请求,但未被回收(默认keepAliveTime为60秒);若线程停留在业务方法调用栈,则是请求处理慢导致线程被占用。ThreadPoolTaskScheduler线程:这类线程多对应定时任务或延迟任务,正常状态是等待下一次调度,典型栈信息:
java.lang.Thread.State: TIMED_WAITING (parking) at jdk.internal.misc.Unsafe.park(Native Method) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:1672) at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1182)若此类线程数量异常,需检查是否存在大量重复提交的调度任务,或任务执行时间超过调度间隔导致线程池扩容。
2. 验证Tomcat线程池配置合理性
Spring Boot 2.7.x默认Tomcat线程池参数为:maxThreads=200、acceptCount=100、minSpareThreads=10、connectionTimeout=20000。针对压测场景需重点关注:
- 通过Actuator指标
tomcat.threads.busy、tomcat.threads.current、tomcat.requests.active确认线程池是否饱和 - 若请求处理耗时较长(如>1s),50并发下每秒仅能处理50个请求,100000请求需2000秒,远超ab的
-s 300超时时间,会导致压测中断 - 调整Tomcat配置优化并发能力:
server.tomcat.threads.max=300 server.tomcat.threads.min-spare=50 server.tomcat.accept-count=200 server.tomcat.connection-timeout=60000
3. 排查业务链路瓶颈
- 用Arthas的
trace命令跟踪请求入口方法,定位耗时最长的环节:arthas trace com.your.package.controller.MyController myMethod -n 10 - 重点检查数据库查询、远程调用、IO操作等环节,是否存在慢SQL、连接池不足(如HikariCP默认
maximum-pool-size=10,50并发下会导致线程等待连接)
4. 优化ZGC配置
当前JVM配置Xms248MB、Xmx3968MB,ZGC建议Xms与Xmx设为相同值,避免内存频繁扩容引发的GC停顿:
-Xms3968M -Xmx3968M -XX:+UseZGC -XX:+ZGenerational -XX:+PrintGC
通过GC日志确认是否存在频繁GC或停顿过长影响请求处理。
5. 规范ThreadPoolTaskScheduler使用
- 若应用中使用
@Scheduled或手动创建调度器,需确保:- 任务执行时间小于调度间隔,避免线程被长期占用
- 配置合理的线程池大小,避免因任务排队导致线程池扩容:
@Configuration public class SchedulerConfig { @Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(5); scheduler.setThreadNamePrefix("scheduler-"); scheduler.setWaitForTasksToCompleteOnShutdown(true); return scheduler; } }
可能的根因总结
- 业务请求处理链路存在慢操作(如慢SQL、远程调用超时),导致线程被长期占用,后续请求排队超时
- Tomcat线程池或数据库连接池配置不足,无法支撑50并发的请求压力
- ZGC内存初始值过小,引发频繁内存扩容与GC停顿,影响请求处理效率
- ThreadPoolTaskScheduler因大量调度任务或任务执行超时,导致线程池扩容,多余线程进入TIMED_WAITING状态
内容的提问来源于stack exchange,提问作者rios0rios0
相关产品推荐
相关产品推荐

