ThreadPoolTaskExecutor无法创建新线程致RestTemplate停发请求排查求助
线程池与RestTemplate异常问题排查与修复
一、应用环境与配置
本Java应用依赖及服务版本:
- Java 7
- Tomcat 7
- Spring 4.3.6
核心业务方法:
public void submitSms(final SmppMoSMS smppMoSMS) { logger.info("** SMS received from SMSC [{}]", smppMoSMS.toString()); int i = Thread.activeCount(); logger.info("Point 1: Active thread count : [{}]", i); taskExecutor.execute(new Runnable() { @Override public void run() { try { logger.info("Thread start "); logger.debug("SMS routing to URL [{}] [{}]", smppMoSMS.getOperator(), smppMoSMS.getApplicationUrl()); logger.info("SMS routing to URL [{}] [{}]", smppMoSMS.getOperator(), smppMoSMS.getApplicationUrl()); if (smppMoSMS.getApplicationUrl() == null) { return; } HttpEntity<SmppMoSMS> entity = new HttpEntity<>(smppMoSMS, headers); ResponseEntity<String> response = restTemplate.exchange(smppMoSMS.getApplicationUrl(), HttpMethod.POST, entity, String.class); if (HttpStatus.OK == response.getStatusCode()) { logger.info("SMS sending success [{}] [{}] [{}]", smppMoSMS.getOperator(), smppMoSMS.getApplicationUrl(), 200); } else { logger.info("SMS sending fail [{}] [{}] [{}]", smppMoSMS.getOperator(), smppMoSMS.getApplicationUrl(), response.getStatusCode()); } } catch (Throwable e) { logger.error("SMS sending fail [{}] [{}]", smppMoSMS.getOperator(), smppMoSMS.getApplicationUrl()); logger.error("SMS ROUTING FAIL", e); } logger.info("Thread stop "); } }); logger.info("Point 2: Active thread count : [{}]", i); MDC.clear(); }
TaskExecutor Bean配置:
<bean id="threadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" destroy-method="destroy"> <property name="corePoolSize" value="5"/> <property name="maxPoolSize" value="200"/> <property name="queueCapacity" value="10000"/> <property name="allowCoreThreadTimeOut" value="true"/> <property name="waitForTasksToCompleteOnShutdown" value="true"/> </bean>
RestTemplate Bean配置:
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> <constructor-arg> <bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory"> <property name="readTimeout" value="30000"/> <property name="connectTimeout" value="30000"/> </bean> </constructor-arg> </bean>
二、问题现象
- 应用启动初期运行正常,RestTemplate可正常发起HTTP请求,日志输出完整;
- 运行2-3小时后出现异常:
- RestTemplate不再生成HTTP请求(已通过Tcpdump及REST API服务器日志确认);
- 仅输出如下日志:
2022:10:01 07:17:10.623 INFO [correlationId=1664588830622, sequence=2244] ** SMS received from SMSC [SmppMoSMS{message='Test by developer ', senderAddress='94XXXXXXX', recipientAddress='94XXXXX', encoding='0', operator='XXXXXXXX', ncsType='null', correlationId='1664588830622'}] 2022:10:01 07:17:10.623 INFO [correlationId=1664588830622, sequence=2244] Active thread count : [114] 2022:10:01 07:17:10.623 INFO [correlationId=1664588830622, sequence=2244] Thread stop 2022:10:01 07:17:10.623 INFO [correlationId=1664588830622, sequence=2244] Active thread count : [114]
- 活跃线程数维持114不再增长;
- 重启应用或Tomcat后服务恢复正常。
三、补充说明
该应用已稳定运行多年,近2个月未进行任何代码或配置变更,却突发此问题。推测:即使配置允许创建最多200个线程,线程池也无法创建新线程。
问题解答
1. 导致线程池无法创建新线程的可能原因
- 线程池队列积压,未触发扩容:ThreadPoolTaskExecutor的逻辑是先填满
queueCapacity(10000),才会创建超过corePoolSize的线程。如果队列中积压大量任务,且核心线程都因HTTP请求阻塞(如等待响应超时),新任务只会进入队列,不会触发maxPoolSize扩容。 - 线程阻塞耗尽线程池:当前RestTemplate未配置HTTP连接池,每次请求都会创建新连接。若目标API响应缓慢或超时,线程会被长时间阻塞(readTimeout=30s),大量线程占用后,线程池无法释放资源,无法创建新线程。
- JVM内存限制:Java 7中每个线程默认占用1MB栈内存,若JVM内存配置不足(如-Xmx过小),创建新线程时会抛出OutOfMemoryError,导致线程池无法扩容。
- Tomcat线程池耗尽:Tomcat自身的Connector线程池(maxThreads)若被占满,会间接影响应用内线程池创建新线程的能力。
2. 其他可能的假设
- RestTemplate连接泄漏:HttpComponentsClientHttpRequestFactory若未正确释放连接,会导致连接耗尽,后续请求无法建立连接,线程阻塞在获取连接阶段。
- 目标API服务异常:目标REST API服务器故障,返回慢或无响应,导致大量线程阻塞在HTTP请求环节,线程池被占满。
- MDC内存泄漏:主线程执行
MDC.clear(),但子线程继承了MDC内容,若未及时清理,会导致线程上下文无法回收,间接影响线程池。 - JVM内存泄漏:长时间运行后应用存在内存泄漏(如未释放的对象引用),导致JVM频繁GC甚至OOM,影响线程创建。
3. 进一步排查方法
- 线程dump分析:出现问题时执行
jstack <pid>获取线程栈,查看是否大量线程处于WAITING/TIMED_WAITING状态,是否卡在RestTemplate请求步骤。 - 内存dump分析:用
jmap -dump:format=b,file=heap.bin <pid>导出堆内存,分析是否存在内存泄漏,以及线程池队列的任务数量。 - 线程池指标监控:自定义监控或用Spring Actuator,实时监控
corePoolSize、activeCount、queueSize等指标,确认队列是否已满、线程是否阻塞。 - 目标API状态排查:检查目标API服务器日志,确认请求是否到达、是否存在响应缓慢;用curl/postman测试目标API响应时间。
- JVM配置检查:查看JVM的
-Xmx、-Xms、-Xss参数,确认内存是否足够创建新线程;查看GC日志,确认是否有频繁Full GC或OOM。 - HTTP连接状态监控:用netstat/ss命令查看应用与目标API的连接数,确认是否存在大量TIME_WAIT或ESTABLISHED状态的连接。
4. 修复建议
- 给RestTemplate配置HTTP连接池:限制最大连接数,避免每次请求创建新连接,减少线程阻塞:
<bean id="httpClient" class="org.apache.http.impl.client.HttpClients"> <factory-method name="custom"> <bean class="org.apache.http.impl.client.HttpClientBuilder"> <property name="maxConnTotal" value="200"/> <property name="maxConnPerRoute" value="100"/> </bean> </factory-method> </bean> <bean id="requestFactory" class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory"> <constructor-arg ref="httpClient"/> <property name="readTimeout" value="30000"/> <property name="connectTimeout" value="30000"/> </bean> <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> <constructor-arg ref="requestFactory"/> </bean>
- 调整线程池参数:减小队列容量,触发线程扩容;设置空闲线程存活时间,及时回收资源:
<bean id="threadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" destroy-method="destroy"> <property name="corePoolSize" value="10"/> <property name="maxPoolSize" value="200"/> <property name="queueCapacity" value="1000"/> <!-- 减小队列容量,触发线程扩容 --> <property name="keepAliveSeconds" value="60"/> <property name="allowCoreThreadTimeOut" value="true"/> <property name="waitForTasksToCompleteOnShutdown" value="true"/> </bean>
- 优化业务逻辑:在子线程末尾添加
MDC.clear();提前过滤applicationUrl为空的任务;为HTTP请求添加额外超时保护。 - 添加监控告警:集成监控工具(如Prometheus+Grafana),监控线程池状态、HTTP请求成功率等指标,设置告警阈值。
- 升级依赖版本:考虑升级Java(Java7已停止支持)、Spring和Tomcat版本,修复旧版本中线程池或HTTP客户端的已知bug。
内容的提问来源于stack exchange,提问作者Udara Seneviratne
相关产品推荐
相关产品推荐

