bigywater.tech
Java · 396

Shutdown Hook:进程退出时如何做最后清理

Shutdown Hook 是进程最后一次做清理的机会,但它不是保证。这篇用真实信号与退出路径量出:什么情况下运行、运行时有哪些限制、被强杀时清理做到了哪里,以及多个 hook 之间没有顺序。

130 / 143 / 129
SIGINT / SIGTERM / SIGHUP 下 JVM 的退出码(hook 都运行了;kill -9 与 Runtime.halt 不运行)
3 / 10
SIGTERM 后 1 秒被 kill -9 时,3 秒的清理完成的步骤数
4 → 3 → 5 → 1 → 2
5 个附加 hook 的一次启动顺序(三次运行的顺序各不相同,开始时刻只差 0–1 ms)

哪些退出方式运行 hook

实测 · 本机执行输出
  main 正常返回                → 退出码 0;hook 运行完了
  System.exit(3)           → 退出码 3;hook 运行完了
  main 抛出未捕获异常             → 退出码 1;hook 运行完了
  Runtime.halt(4)          → 退出码 4;hook 没有运行
  SIGINT(Ctrl-C)           → 退出码 130;hook 运行完了
  SIGTERM(kill)            → 退出码 143;hook 运行完了
  SIGHUP(终端关闭)             → 退出码 129;hook 运行完了
  SIGQUIT(kill -3,线程转储)    → 进程仍然活着(被我杀掉);hook 没有运行
  SIGKILL(kill -9)         → 被信号 9 杀死(SIGKILL);hook 没有运行

多个 hook

实测 · 本机执行输出
  第 1 次:启动顺序 4 → 3 → 5 → 1 → 2;各 hook 开始时刻的跨度 1 ms;线程名 extra-1,extra-2,extra-3,extra-4,extra-5
  第 2 次:启动顺序 3 → 5 → 1 → 4 → 2;各 hook 开始时刻的跨度 0 ms;线程名 extra-1,extra-2,extra-3,extra-4,extra-5
  第 3 次:启动顺序 4 → 2 → 5 → 1 → 3;各 hook 开始时刻的跨度 0 ms;线程名 extra-1,extra-2,extra-3,extra-4,extra-5

慢 hook 与强杀

实测 · 本机执行输出
  被信号 9 杀死(SIGKILL);hook 完成的清理步骤 3 / 10;最后一条:[ 1765 ms] 清理步骤 3;hook 是否结束 = False
  对照:不 kill -9:退出码 143,用时 4.0 s,清理步骤 10 / 10,hook 是否结束 = True
  hook 里 sleep 30 秒:SIGTERM 后 3 秒,进程仍然活着;hook 日志 ['[  824 ms] hook 开始,线程 my-hook']

关机期间的限制

实测 · 本机执行输出
  hook 里再 addShutdownHook:关机期间 addShutdownHook 抛出 IllegalStateException:Shutdown in progress
  hook 里调用 System.exit(9):进程仍然活着(死锁,被我杀掉);日志 hook 开始,线程 my-hook | hook 里调用 System.exit(9)
  hook 里调用 Runtime.halt(9):退出码 9;日志 hook 开始,线程 my-hook | hook 里调用 Runtime.halt(9)
  daemon 线程在 hook 运行期间(823–1528 ms):daemon 线程仍然写了 2 条日志(没有被停掉)

非 daemon 线程

实测 · 本机执行输出
  退出码 0,用时 1.6 s;日志 [    7 ms] main 返回,非 daemon 线程还在跑 | [ 1509 ms] 非 daemon 线程结束 | [ 1514 ms] hook 开始,线程 my-hook | [ 1515 ms] hook 结束

分步演示

Shutdown Hook
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;信号由 Python os.kill 发送给 java 进程;hook 把日志写到文件(避免 stdout 缓冲)
    • 没有验证:Windows 的 Ctrl-C / 服务停止、Linux 容器里 PID 1 的信号语义、Docker / Kubernetes 的 terminationGracePeriod、Spring Boot 的 graceful shutdown、JVM 因 OOM 或 SIGSEGV 崩溃时的 hook、日志框架(Log4j / Logback)在 hook 里的行为
    京ICP备2026067502号-1京公网安备11010502063487号