Java main方法结束后Timer对象GC与cancel()执行逻辑疑问
Java Timer 代码逻辑与T.cancel()调用问题解答
你的核心认知误区
你的推论建立在两个完全错误的前提上,所以才会得出“会抛出异常”的错误结论:
- 误区1:认为main方法执行结束、main线程终止后,JVM就会立刻退出,main方法内定义的局部变量会被垃圾回收
- 误区2:认为Timer实例T在main线程结束后就没有引用,会被GC回收
实际运行逻辑拆解
1. Timer 本身会启动独立的非守护线程
java.util.Timer 的无参构造方法默认会创建一个非守护的用户工作线程,专门负责任务调度。JVM进程的退出条件是所有存活的非守护用户线程全部执行完毕,和main线程是否结束没有关系。也就是说main方法跑完、main线程销毁后,Timer的调度线程还会正常存活,JVM进程不会终止。
2. Timer实例T全程存在强引用,不可能被GC回收
垃圾回收器只会回收“从GC Roots出发不可达”的对象,而你的代码里T的强引用链全程是存活的:
- 你定义的匿名内部类
TimerTask实例在run方法中直接访问了局部变量T,Java编译器会自动把T作为匿名内部类的成员变量保存,持有T的强引用 - Timer实例本身持有任务队列的引用,队列中持有这个TimerTask实例的引用
- Timer的工作线程持有Timer实例本身的引用,而工作线程是存活的GC Roots节点
整条引用链完全可达,T对象根本不会被垃圾回收,自然不存在“回收后调用方法抛异常”的可能。
3. 这段代码的实际执行流程
- main线程启动,创建Timer实例,Timer同步启动独立的调度工作线程
- 配置任务首次触发时间为2021年10月30日23:59:54,调度线程到达指定时间后,每隔1000毫秒执行一次Birthday任务
- 前5次任务执行时依次打印5、4、3、2、1,每次执行后计数器i减1
- 第6次执行时i值为0,打印
Happy Birthday John Doe,随后调用T.cancel():这个方法会终止Timer的调度工作线程,清空所有待调度任务 - 调度线程终止后,JVM内没有存活的非守护线程,进程正常退出
补充说明:如果你创建Timer时调用的是带布尔参数的构造
new Timer(true),会把调度线程设为守护线程,这种情况下main线程结束后JVM会直接退出,不会等待定时任务执行。
内容的提问来源于stack exchange,提问作者Eitanos30
相关产品推荐
相关产品推荐

