Spring Boot应用抛出异常后需执行Ctrl-C才能恢复请求处理的原因咨询
这问题我碰到过好几次了,结合Spring Boot的运行机制,大概率是下面这几个原因造成的:
请求处理线程被异常卡住,线程池耗尽
Spring Boot默认用Tomcat作为容器,它靠线程池来处理REST请求。如果某个请求抛出异常后,线程没有正常退出——比如异常触发了一个无超时的阻塞操作(比如死锁、等待一个永远不会释放的锁、调用了没有超时设置的IO接口),这个线程就会一直卡在阻塞状态,占着线程池的资源。当线程池里的可用线程被耗尽后,后续的请求就只能排队等待,看起来像是应用“卡住”了。
而你按下Ctrl-C时,会给进程发送SIGINT中断信号,JVM收到这个信号后会强制中断所有处于阻塞状态的线程,把这些线程从卡死的状态拉出来,线程池重新有可用线程,之前排队的请求自然就能继续处理了。全局异常处理器存在阻塞逻辑
如果自定义了@ControllerAdvice这类全局异常处理器,要是处理异常的代码里有问题——比如在捕获异常后做了同步阻塞的操作(比如调用远程接口没设超时、等待某个锁),那处理异常的线程就会一直卡在这里,占住线程池的名额。Ctrl-C的中断信号会打断这个阻塞的异常处理流程,线程被释放,后续请求就能正常被处理了。JVM中断信号触发了资源清理
SIGINT信号不仅会中断线程,还会触发JVM的一些清理钩子。比如如果应用存在资源泄漏(比如未关闭的数据库连接、IO流),这些泄漏的资源可能导致线程无法被正常回收,线程池逐渐被占满。中断信号会强制JVM清理这些泄漏的资源,让被占用的线程释放出来,线程池恢复可用。
排查建议:
- 先看应用日志,重点看异常发生时的堆栈信息,定位异常发生的环节,有没有涉及阻塞调用;
- 当应用卡住时,用
jstack <进程ID>命令导出线程堆栈,查看哪些线程处于BLOCKED或WAITING状态,找到卡死的根源; - 检查Tomcat线程池配置(比如
server.tomcat.max-threads),如果核心线程数设置得太小,也容易出现线程耗尽的情况。
内容的提问来源于stack exchange,提问作者B.NAFFETI

