避免终结器拖慢GC的核心是禁用finalize:删除所有finalize方法(含空实现),用try-with-resources或Cleaner替代资源清理,并通过JVM参数或JFR监控终结器压力。

终结器队列本身不会直接“变慢”,真正拖慢垃圾回收的是 终结器引用对象的处理机制 —— 它让本该一次回收的对象,被迫经历两次 GC 周期,还依赖一个低优先级线程执行 finalize()。避免它影响 GC 性能,核心是 **不给 JVM 创建终结器引用的机会**。
彻底禁用 finalize 逻辑
只要类没重写 Object 的 finalize() 方法,JVM 就不会为其实例生成终结器引用,也就不会入队、不会触发 Finalizer 线程调度。这是最根本的解决方式:
- 删除所有重写的 finalize() 方法,哪怕只是空实现也不行
- 检查第三方库(尤其老版本 IO 或 NIO 工具类),确认其是否含 finalize 实现;必要时升级或封装替代
- 使用静态代码分析工具(如 SpotBugs)配置规则,禁止 finalize 方法被定义
用 try-with-resources 替代资源清理
finalize 常被误用于释放文件句柄、Socket、DirectBuffer 等非托管资源。但这些场景完全可用确定性方式替代:
- 让资源类实现 AutoCloseable 接口,用 try-with-resources 保证 exit 时立即 close()
- 对 ByteBuffer.allocateDirect() 创建的直接内存,优先使用 Cleaner(Java 9+)而非继承 PhantomReference + finalize
- 数据库连接、流操作等统一走连接池或封装工具类,内部完成 close 调用,不依赖对象生命周期
监控与识别残留终结器压力
即使项目未主动写 finalize,某些 JDK 内部类(如 FileInputStream)在旧版本中仍含 finalize。可通过以下方式确认实际影响:
立即学习“Java免费学习笔记(深入)”;
- 启动参数加 -XX:+PrintGCDetails -XX:+PrintReferenceGC,观察日志中是否出现 “Finalize” 相关统计
- JDK 9+ 可用 jcmd <pid> VM.native_memory summary 查看 Finalizer 线程栈状态
- 用 JFR(Java Flight Recorder)录制运行时事件,筛选 FinalizerThread 执行耗时和排队长度
必要时显式清除终结器注册(仅限兼容兜底)
极少数遗留系统无法移除 finalize,可考虑在对象明确失效后主动解除注册(需 JDK 14+ 或通过反射):
- 调用 java.lang.ref.Cleaner.create() 替代 finalize,并在业务逻辑中显式 clean()
- 对必须保留的旧类,可在构造后立即调用 sun.misc.Cleaner 类(不推荐,属内部 API)尝试注销,但稳定性无保障
- 更稳妥的做法是:将该类实例包装进 WeakReference,外部持有强引用控制生命周期,避免其进入 F-Queue


















