JVM 如何响应 kill 信号?我们平时注册的
Runtime.getRuntime().addShutdownHook()
究竟是如何执行的?这篇文章带你从源码层深入理解 JVM 的信号处理机制与优雅停机设计。
现实中的场景:为什么要"优雅停机"?
在日常开发中,我们经常会遇到这些需求:
- 微服务部署在 K8s,POD 被 kill 时要释放资源;
- 定时任务中断前需要保存进度;
- IM 服务需要向其他服务发出"我下线了"的通知。
这些行为的触发,往往依赖 JVM 提供的"关闭钩子(Shutdown Hook)"机制。
什么是 Shutdown Hook?
Java 提供了一个方式,在 JVM 即将退出时注册一段"善后代码",用来清理资源、保存数据等。使用方式非常简单:
java
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("程序即将关闭,释放资源中...");
}));
当 JVM 收到如 SIGINT
(Ctrl+C)、SIGTERM
(kill)、System.exit()
等退出信号时,就会执行所有已注册的钩子线程。
JVM 是如何捕获信号的?
操作系统的信号机制简述
在类 Unix 系统中(如 Linux、macOS),程序运行时可以接收操作系统发送的"信号",如:
信号 | 名称 | 含义 |
---|---|---|
2 | SIGINT | Ctrl+C 发送的中断信号 |
15 | SIGTERM | kill 默认发送的终止信号 |
9 | SIGKILL | kill -9 强制杀死,不可捕获 |
1 | SIGHUP | shell 退出后通知子进程关闭 |
JVM 启动时会注册自己的信号处理器,以便拦截上述信号并优雅退出。
JVM Signal 处理源码分析
在 HotSpot 中,有一部分专门负责处理 OS 信号的代码,位于 src/hotspot/os/
目录下(不同平台分别实现)。
以 Linux 为例,os_linux.cpp
中的代码片段:
cpp
SignalHandler(int sig, siginfo_t* info, void* uc) {
// 判断信号类型
if (sig == SIGTERM || sig == SIGINT || sig == SIGHUP) {
// 调用 JVM 层的 shutdown hook 执行函数
JVM_HandleSignal(sig);
}
...
}
JVM_HandleSignal
最终会触发 JVM 的"退出路径",包括:
- 停止所有非守护线程
- 执行 shutdown hooks
- 调用 exit 函数
Shutdown Hook 执行流程解析
当 JVM 决定"关闭"时,会执行以下步骤:
- 冻结所有用户线程;
- 执行所有通过
addShutdownHook()
注册的线程; - 执行 finalizers(如果启用了
System.runFinalizersOnExit(true)
,不推荐); - 真正退出进程。
JVM 会创建一个内部线程,按注册顺序串行执行所有 Hook 线程。注意:
- 如果有某个 Hook 卡住(阻塞不退出),JVM 就不会退出;
- Hook 的执行顺序无法保证,也不能互相依赖;
- Hook 抛出异常不会影响其他 Hook。
特殊场景下 Shutdown Hook 不会触发?
是的,有两个场景要特别注意:
❌ kill -9 <pid>
(发送 SIGKILL)
这个信号无法被任何程序捕获或拦截,直接强制杀死进程,Hook 不会执行。
❌ System.halt(0)
这个方法会绕过所有 ShutdownHook 和 finalizer,直接终止进程:
java
Runtime.getRuntime().halt(0); // 无情终止,无人能救
实战:一个优雅停机的例子
java
public class GracefulShutdownDemo {
public static void main(String[] args) throws Exception {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("服务正在关闭,请稍候...");
try {
Thread.sleep(2000);
System.out.println("清理完成,退出成功!");
} catch (InterruptedException e) {
System.out.println("关闭被打断!");
}
}));
System.out.println("服务已启动,PID: " + ProcessHandle.current().pid());
while (true) {
Thread.sleep(1000);
}
}
}
✅ 测试方式:
- 执行
java GracefulShutdownDemo
- 再运行
kill <pid>
,可以看到优雅退出日志。- 如果是
kill -9 <pid>
,钩子不会执行。
Spring Boot 中的优雅停机机制
Spring Boot 封装了 JVM Hook,并在其中触发 ApplicationContext
的关闭。
java
SpringApplication application = new SpringApplication(MyApp.class);
application.setRegisterShutdownHook(true); // 默认就是 true
application.run(args);
Spring 停机流程:
- 执行 ApplicationContext.close()
- 发布
ContextClosedEvent
- 销毁实现了
@PreDestroy
、DisposableBean
的 Bean - 自动释放资源(线程池、连接池等)
注册停机逻辑的方式
实现 DisposableBean
java
@Component
public class ShutdownCleanup implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("释放数据库连接...");
}
}
使用 @PreDestroy
java
@Component
public class MyComponent {
@PreDestroy
public void onExit() {
System.out.println("正在清理缓存...");
}
}
监听关闭事件
java
@Component
public class ShutdownListener {
@EventListener
public void onContextClosed(ContextClosedEvent event) {
System.out.println("收到 Spring 上下文关闭事件!");
}
}
Kubernetes 中的容器优雅停机流程
K8s 关闭 Pod 的流程:
text
1. 调用 preStop(若配置)
2. 发送 SIGTERM 给主进程(如 java)
3. JVM 执行 Shutdown Hook
4. Spring Boot 执行销毁逻辑
5. 等待 terminationGracePeriodSeconds 超时或进程退出
6. 若未退出,则发送 SIGKILL 强制杀死
配置 preStop 调用接口
yaml
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "curl http://localhost:8080/offline"]
Spring Boot 提供接口:
java
@RestController
public class ShutdownController {
@PostMapping("/offline")
public void offline() {
System.out.println("开始下线流程...");
// 通知注册中心下线、设置健康状态等
}
}
设置 terminationGracePeriodSeconds
yaml
spec:
terminationGracePeriodSeconds: 30
这个值决定 Pod 终止前等待应用优雅退出的时间。
在生产系统中的使用建议
建议 | 原因说明 |
---|---|
Hook 内代码要尽快执行完 | Hook 阻塞会阻塞 JVM 退出 |
不建议执行复杂逻辑、远程调用等 | 超时不可控,容易阻塞退出 |
避免多个钩子间的执行依赖 | JVM 不保证执行顺序 |
尽量使用守护线程来处理非 Hook 的工作项 | 避免非守护线程阻止 JVM 退出 |
Kubernetes 中使用 preStop 配合 Hook |
保证容器关闭前先运行 Hook |
- JVM 的优雅停机机制其实设计得非常完善,它通过捕捉操作系统信号并执行关闭钩子,为我们提供了资源清理与通知下线的"最后机会"。理解这套机制,对构建可靠、高可用的服务系统至关重要。
延伸阅读推荐:
- JVM 对信号的全处理链路图
sun.misc.Signal
的手动注册方式- Spring Boot 中的优雅停机机制底层实现(ApplicationContext 的关闭流程)
最后
如果文章对你有帮助,点个免费的赞鼓励一下吧!关注公众号:加瓦点灯, 每天推送干货知识!