1. 线程池导致SpringBoot优雅停机失败的深度排查
最近在项目中遇到一个棘手的问题:SpringBoot应用在调用/actuator/shutdown接口时无法正常关闭。经过一番排查,发现是线程池管理不当导致的。这个问题看似简单,但涉及到了SpringBoot生命周期管理、线程池原理和JVM退出机制等多个知识点。下面我就把完整的排查过程和解决方案分享给大家。
优雅停机(Graceful Shutdown)是生产环境中非常重要的一个特性。它指的是在关闭应用程序时,允许正在处理的请求完成,同时拒绝新的请求进入。SpringBoot通过actuator模块提供了这个功能,只需要简单配置即可启用:
yaml复制server:
port: 8888
shutdown: graceful
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: shutdown
同时需要添加actuator依赖:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题现象与初步分析
2.1 异常现象描述
正常情况下,调用localhost:8888/actuator/shutdown接口后,应用应该会在处理完当前请求后关闭。但在我们的场景中:
- 直接调用shutdown接口时,应用可以正常关闭
- 但在执行某个特定业务后,再调用shutdown接口,应用虽然会执行shutdown流程,但最终进程仍然存在
- 查看日志,确实能看到shutdown的日志输出,但进程ID没有被kill
2.2 初步排查思路
首先怀疑是有资源未正确释放,导致SpringBoot认为应用仍在运行。可能的排查方向包括:
- 数据库连接池未关闭
- 文件或网络资源未释放
- 自定义线程池未关闭
- 定时任务仍在运行
3. 深入排查过程
3.1 线程状态分析
使用jstack生成线程快照是排查这类问题的有效手段:
bash复制# 生成线程快照
jstack -l pid > threads.txt
# 查询非守护进程(因为非守护线程会阻止JVM退出)
grep -n '" ' threads.txt | grep -v daemon
分析线程快照后发现:
- 大部分线程都是框架或JVM自身的守护线程
- 存在一个名为"pool-4-thread-1"的非守护线程处于WAITING状态
- 该线程的堆栈显示它来自ThreadPoolExecutor,正在等待任务
关键线程堆栈信息如下:
code复制"pool-4-thread-1" #230 prio=5 os_prio=31 cpu=0.21ms elapsed=252.08s tid=0x00000001642cf800 nid=0x9a07 waiting on condition [0x000000017aaf6000]
java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.13/Native Method)
- parking to wait for <0x0000000701e8af30> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.13/LockSupport.java:341)
at java.util.concurrent.locks.Abst
