jvm不退出是因为存在未终止的非守护线程;需通过thread.getallstacktraces或jstack检查runnable/timed_waiting线程,将自建线程设为守护线程(start前调setdaemon(true)),并显式关闭第三方线程池与资源。

主方法执行完 JVM 却不退出,通常是因为存在非守护线程(user thread)仍在运行。JVM 只有在所有非守护线程都终止后才会正常退出;只要还有一个活着的非守护线程,它就会一直挂起等待。
确认是否有残留的非守护线程
最直接的方式是打印当前所有线程快照,观察哪些线程仍在 RUNNABLE 或 TIMED_WAITING 状态:
- 在程序末尾加一句 Thread.getAllStackTraces().keySet().forEach(System.out::println);
- 或用 jstack
查看运行中 Java 进程的线程栈(需在命令行操作) - 重点关注名字含 pool、http、netty、grpc、Timer、AWT-EventQueue 的线程——它们常默认以非守护方式启动
将后台任务线程设为守护线程
如果你自己创建了线程(比如轮询、日志刷盘、心跳检测),务必在 start() 前调用 setDaemon(true):
-
错误写法:
new Thread(() -> { while (true) doWork(); }).start();→ 默认是非守护线程 -
正确写法:
Thread t = new Thread(() -> { while (true) doWork(); }); t.setDaemon(true); t.start(); - 注意:必须在 start() 之前设置,否则抛 IllegalThreadStateException
正确关闭第三方资源和线程池
很多框架内部会创建自己的线程池(如 OkHttp、HikariCP、Spring Scheduler、Logback 的 AsyncAppender),它们默认不随主线程结束而关闭:
- 显式调用 shutdown() + awaitTermination() 关闭自建的
ExecutorService - 使用 try-with-resources 或 @PreDestroy(Spring)/ Closeable 接口释放资源
- 对 OkHttp:调用
client.dispatcher().executorService().shutdown()和client.connectionPool().evictAll() - 对 Netty:确保
EventLoopGroup.shutdownGracefully()被调用并 await 完成
避免隐式创建非守护线程的常见陷阱
有些 API 表面看不出开线程,实则悄悄启用了后台线程:
-
java.util.Timer:构造出的 Timer 线程默认非守护 → 改用
new Timer(true)(true 表示守护) -
Swing/AWT:哪怕只调了一次
SwingUtilities.invokeLater,也可能触发 AWT EventQueue 启动非守护线程 → 若无 GUI 需求,避免引入相关类 -
Java Agent 或监控 SDK(如 SkyWalking、Prometheus):部分版本会在初始化时启动采集线程且未设守护 → 查阅对应文档,确认是否支持配置
daemon=true或提供 shutdown 方法










