Tomcat不复用等待线程反而创建新线程问题排查求助
问题描述
将Jira 8.13部署在Apache Tomcat/8.5.60集群环境中,部分节点会随机出现不复用处于WAITING状态的空闲HTTP线程、反而创建新线程的异常情况。最终Tomcat达到maxThreads上限后停止接收新请求,但应用其他模块仍可正常运行。Tomcat及应用日志无异常输出,仅能通过重启Tomcat恢复。
相关线程dump(为等待任务的HTTP线程正常状态):
http-nio-8080-exec-2618 sun.misc.Unsafe.park(Native Method) java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078) java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:467) org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:90) org.apache.tomcat.util.threads.TaskQueue.poll(TaskQueue.java:33) java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1073) java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) java.lang.Thread.run(Thread.java:748)
当前Connector配置:
<Connector port="8080" relaxedPathChars="[]|" relaxedQueryChars="[]|{}^\`"<>" maxThreads="200" minSpareThreads="25" enableLookups="false" maxHttpHeaderSize="8192" protocol="HTTP/1.1" useBodyEncodingForURI="true" redirectPort="8443" acceptCount="100" connectionTimeout="120000" disableUploadTimeout="true" bindOnInit="false" secure="true" scheme="https" proxyName="*" proxyPort="443" keyAlias="jira" keystoreFile="/* /jira.jks" keystorePass="*" keystoreType="JKS"/>
可能的原因
- Tomcat TaskQueue复用逻辑缺陷:Tomcat默认的
TaskQueue继承自LinkedBlockingQueue,在高并发或特定任务场景下,线程池可能误判无空闲线程,进而创建新线程。Tomcat 8.5.x部分版本存在线程池调度的逻辑偏差,在任务队列状态变化时的判断不够严谨。 - ThreadLocal变量泄漏:Jira应用中若存在未清理的ThreadLocal绑定资源,会导致线程虽处于WAITING状态,但线程池无法正确识别其可复用性,被迫创建新线程。
- 连接状态与线程标记不匹配:当前
connectionTimeout=120000(2分钟),若客户端异常断开连接,Tomcat可能未及时将对应线程标记为可复用,导致线程池误判。 - 集群负载不均衡:集群节点请求分布不均,部分节点长期高负载,触发线程池调度逻辑异常,无法正确识别空闲线程。
调试方法
- 实时监控线程池指标:用JConsole、VisualVM等JMX工具监控Tomcat线程池的
currentThreadsBusy、currentThreadCount、queueSize等指标,记录异常发生时的数值变化,对比空闲线程数与新线程触发条件。 - 捕获异常时的全量dump:问题出现时立即执行
jstack获取全量线程dump、jmap获取堆dump,分析是否存在ThreadLocal泄漏、线程实际状态及任务队列情况。 - 调整Tomcat日志级别:修改
conf/logging.properties,将org.apache.tomcat.util.threads的日志级别设为FINE,观察线程调度、任务分配的详细日志,定位复用失败的触发点。 - 模拟场景复现:在测试环境模拟高并发、客户端异常断开、长连接等场景,验证问题是否与特定请求模式相关。
临时解决方案
你考虑的配置独立Executor并设置maxIdleTime是可行的,具体配置建议:
- 在
server.xml中添加独立Executor:
<Executor name="tomcatThreadPool" namePrefix="http-nio-8080-exec-" maxThreads="200" minSpareThreads="25" maxIdleTime="60000" maxQueueSize="100"/>
- 修改Connector引用该Executor:
<Connector port="8080" relaxedPathChars="[]|" relaxedQueryChars="[]|{}^\`"<>" enableLookups="false" maxHttpHeaderSize="8192" protocol="HTTP/1.1" useBodyEncodingForURI="true" redirectPort="8443" acceptCount="100" connectionTimeout="120000" disableUploadTimeout="true" bindOnInit="false" secure="true" scheme="https" proxyName="*" proxyPort="443" keyAlias="jira" keystoreFile="/* /jira.jks" keystorePass="*" keystoreType="JKS" executor="tomcatThreadPool"/>
maxIdleTime=60000(1分钟)会让空闲线程超时后自动销毁,避免堆积无法复用的线程,同时独立Executor的调度逻辑可规避默认线程池的复用缺陷。
内容的提问来源于stack exchange,提问作者Кирилл Ленченков
相关产品推荐
相关产品推荐

