finalize()自JDK9起被弃用、JDK18彻底移除,因其执行时间不确定、可能引发内存泄漏、影响GC效率且无法保证调用;应优先使用try-with-resources或显式close,非关键资源可用Cleaner替代,高级场景可选PhantomReference。

Java 中 finalize() 方法自 JDK 9 起已被标记为 @Deprecated,JDK 18 彻底移除(仅保留声明但不执行),不再推荐用于资源清理。它存在严重问题:执行时间不确定、可能引发内存泄漏、影响 GC 效率,且无法保证调用。现代 Java 应该用更可控、更可靠的方式替代。
为什么 finalize 不再安全可靠
finalize() 的核心缺陷不是“写不好”,而是 JVM 设计上就放弃了对其行为的保障:
- GC 不保证何时(甚至是否)调用它,对象可能长期驻留堆中;
- 若
finalize()抛出未捕获异常,JVM 会静默吞掉,且该对象不会被再次终结; - 重写
finalize()会使对象在第一次 GC 时进入“finalization queue”,延长生命周期,加剧 GC 压力; - 无法与 try-with-resources 或显式 close 配合,容易遗漏清理逻辑。
优先使用 try-with-resources 或显式 close
对于持有文件句柄、网络连接、数据库连接等可关闭资源的对象,标准做法是实现 AutoCloseable 接口,并配合 try-with-resources:
public class ManagedResource implements AutoCloseable {
private final FileChannel channel;
public ManagedResource(String path) throws IOException {
this.channel = FileChannel.open(Paths.get(path), READ);
}
@Override
public void close() throws IOException {
if (channel != null && channel.isOpen()) {
channel.close();
}
}
}
// 使用方式(自动确保 close)
try (ManagedResource r = new ManagedResource("data.txt")) {
// 使用资源
} // ← close() 在这里被自动调用
用 Cleaner 替代 finalize 处理非关键资源释放
Cleaner 是 JDK 9 引入的轻量级、无栈依赖的清理机制,适用于必须在对象不可达后执行的“尽力而为”清理(如释放 native 内存、注销监听器等),但不能替代显式 close。
立即学习“Java免费学习笔记(深入)”;
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- Cleaner 不依赖对象的
finalize()方法,也不阻塞 GC; - 清理动作由独立线程异步执行,不干扰 GC 线程;
- 需手动注册对象和清理逻辑,且建议持有 Cleaner 实例为 static,避免其自身被回收。
示例:
public class NativeBuffer {
private static final Cleaner cleaner = Cleaner.create();
private final ByteBuffer buffer;
private final long address;
public NativeBuffer(int size) {
this.buffer = ByteBuffer.allocateDirect(size);
this.address = ((DirectBuffer) buffer).address();
// 注册清理动作:当 NativeBuffer 不可达时,释放 native 内存
cleaner.register(this, new CleanupAction(address));
}
private static class CleanupAction implements Runnable {
private final long address;
CleanupAction(long address) {
this.address = address;
}
@Override
public void run() {
if (address != 0) {
unsafe.freeMemory(address); // 示例:实际需用 Unsafe 或 JNI
}
}
}
}
注意:Cleaner 不保证立即执行,也不保证一定执行——它只是“尽力而为”。关键资源仍需显式释放。
补充:PhantomReference + ReferenceQueue(高级场景)
如果需要更精细控制(比如记录对象回收日志、触发缓存剔除、做回收审计),可用 PhantomReference 配合 ReferenceQueue。它比 Cleaner 更底层、更灵活,但也更易出错:
- phantom reference 不会阻止对象被回收;
- 只有对象被 GC 回收后,引用才会被加入队列;
- 必须搭配单独的守卫线程轮询队列并执行清理逻辑。
除非有特殊需求(如监控/诊断工具),否则 Cleaner 已足够,无需自行管理 PhantomReference。

















