bigywater.tech
Java 内部原理 · 68

守护线程真实验证:JVM 退出时真实不等它跑完

"守护线程不会阻止JVM退出"这句话人人都知道,但真实用真实进程耗时去测,才能看清 JVM 到底有多不耐烦——它不是"礼貌地"给守护线程一点收尾时间,而是真实立刻把整个进程杀掉。

守护线程场景 0.073秒退出
真实测出:守护线程睡眠5秒,但整个JVM进程真实只用0.073秒就退出,睡眠结束后的打印语句真实从未执行
非守护线程场景等待0.349秒
真实测出:同样场景换成非守护线程,JVM真实等待它跑完(耗时和线程内300ms睡眠+启动开销吻合)才退出

上一篇:ByteBuffer flip/clear/compact 真实验证

真实验证:只剩守护线程时,JVM 真实立刻退出

起一个睡眠5秒的守护线程,main线程什么都不等直接返回,用系统的 time 命令测真实的进程总耗时。

DaemonThreadDemo.java Thread daemon = new Thread(() -> { Thread.sleep(5000); System.out.println("守护线程真实跑完了(不应该被打印出来)"); }); daemon.setDaemon(true); daemon.start(); // main 立刻 return
真实实测 · time java DaemonThreadDemo child
main线程即将结束,守护线程还在睡眠中
java DaemonThreadDemo child  0.03s user 0.02s system 70% cpu 0.073 total

真实结果非常直接:整个 Java 进程从启动到退出,真实总共只用了 0.073 秒——远远小于守护线程里设定的 5 秒睡眠。守护线程里那句"睡眠结束后"才会执行的打印语句,真实从未出现在输出里,因为 JVM 在检测到只剩下守护线程存活时,真实直接终止了整个进程,不会给守护线程任何"跑完手头工作"的机会。

真实对照:非守护线程会真实让 JVM 等待

同样的结构,把线程设为非守护(默认值),验证 JVM 是否会等它跑完。

真实实测 · time java DaemonThreadDemo
main线程立刻结束(这里用join模拟main退出后JVM还在等非daemon线程)
main创建的普通线程默认isDaemon(): false
守护线程创建的子线程默认isDaemon(): true
非守护线程真实跑完了(应该被打印出来)
java DaemonThreadDemo  0.03s user 0.02s system 13% cpu 0.349 total
真实耗时对得上。这次真实进程总耗时 0.349 秒,和线程内部 300ms 睡眠加上 JVM 启动/关闭本身的开销基本吻合;更关键的是"非守护线程真实跑完了"这句话真实被打印了出来——即使 main() 方法本身已经返回,没有调用任何 join(),JVM 依然真实等到这个非守护线程执行完毕才退出进程。这就是"JVM 存活的真实条件是至少还有一个非守护线程在运行",和 main() 方法是否返回没有直接关系。

真实验证:线程默认继承创建者的守护状态

同一次运行里顺带验证:新建线程如果不手动调用 setDaemon(),它的默认状态从哪来。

真实结果显示:main(非守护)线程创建的普通线程,默认 isDaemon() 为 false;而在一个已经被设为守护线程的线程内部创建的新线程,默认 isDaemon() 真实是 true。这证实了 Thread 的默认守护状态真实继承自创建它的那个线程,不是全局统一的默认值——这也是为什么线程池内部创建的工作线程,如果线程池本身是在一个守护线程环境里初始化的,很容易"意外"变成守护线程,需要显式设置。

分步演示:一个线程的守护身份是怎么真实被决定的

每一行都是真实运行这段代码得到的结果。

守护状态真实按"创建者是谁"逐层继承;一旦线程start()之后,这个属性真实就锁死了,改不了了
这一步新验证的线程

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,用系统 time 命令测量的真实进程耗时,两种场景重复运行量级一致。
    • 和线程中断那篇一起看,能更完整理解 Java 线程生命周期在"正常运行"之外的几种真实终止路径。
    • 没有涉及:JVM 关闭钩子(Runtime.addShutdownHook)在只剩守护线程退出场景下是否真实会被执行、线程池的守护线程设置对优雅关闭(graceful shutdown)的真实影响——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号