system.exit()仅触发jvm关闭流程,清理由shutdownhook完成;钩子在正常关闭(如system.exit、sigterm、主线程结束)时执行,但不响应kill-9或崩溃;应注册轻量、幂等、带超时的收尾逻辑,避免在web框架中误用。

System.exit() 本身不执行清理,它只是启动 JVM 关闭流程的“开关”。真正做清理工作的,是提前注册的 ShutdownHook。关键不是让 exit 多可靠,而是让它触发钩子、并确保钩子写得稳当。
ShutdownHook 是怎么被触发的
只要 JVM 进入正常关闭流程,已注册的钩子就会被执行,包括:
- 主线程结束,且所有非守护线程都已退出
- 调用 System.exit(0)、System.exit(1) 或任意整数代码
- 用户按 Ctrl+C(发送 SIGINT)
- 系统发送 SIGTERM(如 kubectl delete pod、systemctl stop)
注意:kill -9(SIGKILL)、Runtime.halt()、JVM 崩溃或强制断电时,钩子完全不会运行。
注册钩子的正确姿势
钩子必须在 JVM 关闭前注册,通常放在 main 方法开头、Spring 初始化完成之后,或服务启动成功的第一时间:
- 用 new Thread(() -> { ... }) 包裹清理逻辑,不要提前 start()
- 避免在钩子里再调用 System.exit() 或 Runtime.halt()
- 每个钩子应独立、幂等;多个钩子之间无执行顺序保证
- 可调用 Runtime.getRuntime().removeShutdownHook() 主动注销(较少用)
钩子里该做什么、不该做什么
钩子是“尽力而为”的收尾环节,适合做轻量、确定、有超时保护的操作:
- 显式关闭连接池:dataSource.close()、connectionPool.shutdownNow()
- 通知注册中心下线:registryClient.deregister()
- 刷盘日志缓冲区:LoggerContext.stop()
- 保存临时状态文件(供下次启动识别异常中断)
- 避免阻塞等待:用 CountDownLatch.await(5, TimeUnit.SECONDS) 替代无超时 join()
- 捕获所有异常,不抛出未处理 Exception(否则 stderr 打印但不中断流程)
哪些场景不该依赖 System.exit + Hook
Web 应用和服务框架(如 Spring Boot、Tomcat)已有完整生命周期管理:
- 不要在 Controller 或 Service 中调用 System.exit()
- 应通过 Actuator 的 /actuator/shutdown 端点,或接收 SIGTERM 让容器触发优雅停机
- 若需主动终止,优先用 SpringApplication.exit(context),它会走完整 Bean 销毁流程
- 仅在极早期失败时(如配置加载失败、端口冲突)才用 System.exit(),此时无活跃业务,钩子只需兜底











