bigywater.tech
Java 内部原理 · 18

finalize() 真的能让对象复活:真实验证,但 WeakReference 不会跟着活过来

"finalize() 里能让对象复活"是 finalize 被弃用的原因之一,常被当成一句结论背下来。真实写一个会在 finalize() 里自我复活的类,GC 之后看它到底活没活过来——顺带撞见一个没预料到的真实细节。

resurrected != null
真实验证:finalize() 里把 this 存进静态字段后,GC 结束时这个字段真实变成非 null,对象真的复活了
weakRef.get() = null
意外的真实结果:同一时刻,追踪同一个对象的 WeakReference 却真实已经被清空,复活没能让它恢复

上一篇:notify() 真实测出会卡死:3 次运行,3 次真实卡在个位数

思路:让对象在临终前给自己续命

finalize() 是 JVM 判定对象不可达之后、真正回收它之前调用的方法——如果在这个方法里重新建立一条到 this 的强引用,理论上就能让对象“复活”。

FinalizeResurrect.java(会复活的类) static volatile Object resurrected; static class Zombie { @Override protected void finalize() { resurrected = this; // 复活:在 finalize 里重新建立一条强引用 finalizedLatch.countDown(); } }

用 CountDownLatch 而不是 Thread.sleep 猜时间,是为了让"finalize 是否真的执行了"这件事有一个确定的信号,不靠运气。

真实验证:对象真的复活了,但 WeakReference 没有

去掉唯一的强引用,触发 GC,验证 resurrected 字段和一个追踪同一对象的 WeakReference。

真实实测 · 第一轮 GC
finalize() 是否在 5 秒内真实执行 = true
resurrected 字段是否非 null(对象真的复活了) = true
weakRef.get() 是否仍能真实拿到对象 = false

第三行是这次实验里最意外的真实结果:resurrected 字段明明已经真实指向这个对象(强引用),但 weakRef.get() 却真实返回了 null——两者矛盾吗?不矛盾,这是一个真实的时序问题:GC 在同一轮收集里,处理弱引用(把不再被强引用的 WeakReference 清空)这一步,发生在调用 finalize() 之前。所以真实的顺序是:GC 判定对象只剩 weakRef 这一条弱引用 → 清空 weakRef → 调用 finalize() → finalize() 里对象重新变成强可达。weakRef 已经在第二步被清空了,不会因为后面对象复活而自动恢复——弱引用一旦被清,就是永久清空,不是跟着对象的可达性状态实时联动的。

真实实测 · 再次去掉强引用,触发第二轮 GC
第二轮 GC 后 weakRef.get() = null(应为 null,说明这次真死了,finalize 不会再跑第二次)
JVM 对每个对象只调用一次 finalize(),真实验证:第二次真正变得不可达时,直接被回收,不会再给它第二次复活的机会

对照真实验证:Cleaner 从设计上就不给复活的机会

java.lang.ref.Cleaner 是 finalize() 的现代替代方案,关键区别在于清理动作是一个独立的 Runnable,不持有 this。

真实实测 · Cleaner
清理动作真实执行了(这里访问不到 Resource 的 this,没法复活它)
cleaner 是否在 5 秒内真实执行 = true
weakRef.get() = null(应为 null,对象真实被回收,且没有任何办法复活它)
为什么 Cleaner 更安全?不是因为 Cleaner 运气好没写出复活代码,而是它的 API 设计从根上堵死了这条路:注册给 Cleaner 的清理动作是一个提前构造好的 Runnable,构造时就不允许它捕获对 Resource 实例的引用(一旦捕获,反而会导致这个 Resource 永远不会被判定为不可达,造成新的内存泄漏,这也是 Cleaner 文档里特别强调的注意事项)。真实验证下来,Cleaner 没有给"复活"留任何语言层面的口子,这正是它替代 finalize() 的核心原因之一。

分步演示:一个对象是怎么真实在finalize()里被"救活"的

每一行都是真实运行这段代码得到的结果(本机真实测得,可能需要几轮gc()才触发)。

finalize()里把this重新赋给一个可达的字段,真实能让一个即将被回收的对象死而复生——但这个复活机会真实只有一次
这一步新验证的GC结果

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,finalize() 自 Java 9 起已标记为 deprecated,本文仅作真实原理验证,不建议在新代码里使用。
    • "弱引用没有跟着复活一起恢复"这个真实细节,和ThreadLocal 内存泄漏那篇一样,都是弱引用相关、容易被想当然理解的真实边界情况。
    • 没有涉及:PhantomReference 和 ReferenceQueue 更底层的引用处理机制、GC 具体在哪个阶段处理各类引用的完整顺序——这些留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号