system.exit()仅触发jvm关闭流程,不执行finally、try-with-resources、spring销毁等常规清理;唯一执行的是已注册shutdownhook,但其受限于资源不可用、日志失效、类加载器回收等约束。

System.exit() 本身不执行任何清理,它只是启动 JVM 关闭流程的“开关”。真正负责收尾的是已注册的 ShutdownHook,但它们的执行有明确前提和限制,并非万能兜底。
ShutdownHook 是唯一能执行的 Java 层逻辑
JVM 在收到 System.exit() 后,会进入关闭序列,此时仅以下内容会被执行:
- 所有已注册且未被移除的 ShutdownHook,以注册逆序(后注册的先执行)启动为独立线程运行
- 每个钩子线程必须自行处理异常、超时和幂等性,JVM 不提供保障
- 钩子运行期间,所有非守护线程已被中断,主线程已退出,JVM 不再接受新任务
这些清理动作完全不会发生
System.exit() 会直接跳过所有常规 Java 清理机制:
- try-catch-finally 中的 finally 块(除非 JVM 已开始执行该块但尚未退出)
- try-with-resources 的自动 close() 调用
- @PreDestroy 注解方法、DisposableBean.destroy()、SmartLifecycle.stop()
- Spring 容器的 Bean 销毁流程、上下文关闭事件
- 守护线程或非守护线程中尚未完成的业务逻辑(如数据库写入、文件保存)
钩子执行前后的关键事实
ShutdownHook 的运行环境受限,需注意实际约束:
- 钩子无法依赖其他正在关闭的资源:比如数据库连接池可能已在钩子执行前被 Spring 销毁,此时调用 dataSource.close() 可能抛 NPE
- 日志系统可能已停止:LoggerContext.stop() 需在钩子中显式调用,不能假设 SLF4J 或 Logback 仍可用
- 类加载器可能被回收:热部署或模块化环境中,钩子里引用的类可能已不可达
- 没有执行顺序保证:多个钩子之间互不等待,也不按依赖关系调度
真正安全的退出路径不是靠 exit 触发钩子,而是绕开 exit
在 Web 服务、Spring Boot、微服务等场景中,应避免 System.exit():
- 使用 SpringApplication.exit(context, code):先走完整生命周期销毁,再调用 System.exit()
- 接收 SIGTERM 后调用 ApplicationContext.close():由容器控制关闭节奏
- 通过 Actuator 的 /actuator/shutdown 端点触发优雅停机
- 命令行工具中,用 return 主方法末尾 + try-with-resources + 显式 close(),让 JVM 自然退出










