1. 对象死亡的两次标记过程
第一次标记:当垃圾回收器通过可达性分析发现对象不可达时,会进行第一次标记,并检查是否需要执行 finalize() 方法 。
如果对象未覆盖 finalize() 方法,或该方法已被执行过,则直接判定为“无需执行”,对象会被回收。
否则,对象会被放入 F-Queue 队列,等待执行 finalize() 方法。
第二次标记:垃圾回收器稍后会对 F-Queue 中的对象进行第二次标记。若对象在 finalize() 中成功“复活”(例如将 this 赋值给某个静态变量),则会被移出回收集合;否则将被真正回收 。
2. “自救”的机制与限制
如何自救:在 finalize() 方法中,对象可以通过重新建立引用链来逃脱回收。例如:
@Override
protected void finalize() {
FinalizeEscapeGC.SAVE_HOOK = this; // 将this赋值给静态变量,重新建立引用
}
此时对象在第二次标记时会被判定为“存活” 。
只能自救一次:每个对象的 finalize() 方法最多被调用一次。若对象再次不可达,则无法通过 finalize() 逃脱,如用户代码示例中第二次回收失败的结果所示
3. 为什么说这是“缓刑”阶段?
不确定性: finalize() 方法的执行由低优先级的 Finalizer 线程触发,且不保证执行时机。如果方法执行缓慢或死循环,可能导致其他对象无法及时回收,甚至引发内存回收系统崩溃 。
资源释放不可靠:依赖 finalize() 释放资源(如关闭文件)可能导致泄漏,因为无法确保方法一定执行 。
4. 为什么不推荐使用finalize()?
性能问题: finalize() 会延长对象生命周期,增加垃圾回收的复杂性 。
已被官方弃用:从Java 9开始, finalize() 被标记为废弃,推荐使用以下替代方案 :
try-with-resources :自动关闭资源(如文件流、数据库连接)。
实现 AutoCloseable 接口:通过显式调用 close() 方法释放资源。
PhantomReference :更精细地控制对象回收后的清理操作。
总结比喻
可以把 finalize() 比作“死刑犯的最后一次上诉机会”:如果能在“上诉”( finalize() )中证明自己“不该死”(重新建立引用),就能暂时存活;但上诉机会只能使用一次,且结果充满不确定性。因此,现代Java开发中更推荐通过明确的资源管理机制(如 try-with-resources )来替代这种“危险的自救” 。