finalize方法已被官方废弃停用,因其执行不可控、阻塞GC、易致死锁与内存泄漏;JDK 9弃用、JDK 18标记forRemoval、JDK 21大幅移除;推荐用try-with-resources或Cleaner替代。

finalize 方法的不当使用是 Java 中一种隐蔽但后果严重的内存泄漏源头。它不表现为传统意义上的“对象未被释放”,而是通过干扰 GC 机制、阻塞回收链路、诱发对象复活等方式,让本该被回收的对象长期滞留堆中,最终引发 OutOfMemoryError —— 即使堆内存尚未真正耗尽。
Finalizer 线程卡死导致回收停滞
所有重写了 finalize() 的对象,都会被 JVM 自动注册进一个全局 ReferenceQueue,由单线程的 Finalizer 线程串行处理。这个线程优先级低、无超时、不可中断:
- 只要某个对象的
finalize()执行缓慢(如等待网络响应、持有锁未释放、调用阻塞 I/O),整个队列就会“堵住” - 后续所有待终结对象无法出队,也就无法真正被 GC 回收
- 现象是:老年代占用率持续攀升,
jstat -finalstats显示pending-finalize队列长度不断增长,而堆 dump 中大量对象被java.lang.ref.Finalizer引用链持有
对象复活引发循环泄漏
在 finalize() 内部将 this 赋值给静态变量、缓存集合或监听器列表,会使对象重新获得强引用——这叫“对象复活”。JVM 不阻止这种行为,但它会破坏 GC 的可达性判断基础:
- 复活后的对象不再进入 finalization 队列,但也不会被立即回收;下次 GC 时若再次变为不可达,又可能重新入队
- 典型错误:
static List<Object> cache = new ArrayList<>(); protected void finalize() { cache.add(this); } - 结果是缓存无限膨胀,同一对象反复入队、反复执行
finalize(),资源重复释放、状态错乱,甚至触发StackOverflowError
掩盖真实泄漏点,延误故障定位
很多开发者把 finalize() 当作“兜底保险”,以为漏调 close() 也没关系。这反而让问题更难排查:
立即学习“Java免费学习笔记(深入)”;
- 程序在线上运行数天后才 OOM,日志中找不到明确的资源未关闭痕迹
- 监控只能看到 Finalizer 队列堆积,却无法反向定位是哪个类、哪段代码漏关文件句柄、Socket 或 DirectByteBuffer
- JFR 已不再采集 Finalizer 统计,VisualVM 插件也移除了相关视图,工具链已主动放弃支持
性能开销远超预期
启用 finalize() 不仅影响回收效率,还会显著拖慢对象生命周期:
- 每个重写该方法的对象创建时,JVM 需额外注册到 Finalizer 链表,实测吞吐下降 20% 以上
- 对象至少经历两次 GC 周期才能被真正回收(标记为可终结 → 执行 finalize → 再次判定可回收)
- 微基准测试显示:带
finalize()的对象创建销毁耗时比普通对象慢约 430 倍(5.6ns → 2400ns)
Java 9 起该方法已被标记为 @Deprecated(forRemoval = true),JDK 18 默认禁用 Finalizer 线程,JDK 21 中 HotSpot 已大幅简化其逻辑。它不是“慎用”,而是“已废止”。可靠资源管理必须回归显式控制:用 AutoCloseable + try-with-resources,或 Cleaner(推荐)、PhantomReference 替代。


















