Java 9起finalize()被弃用,Java 18正式移除;推荐使用Cleaner——基于虚引用、线程安全、可预测的轻量级清理机制,需显式注册/注销且不可替代try-with-resources。

Java 9 开始,Object.finalize() 被标记为 @Deprecated,Java 18 正式移除(JEP 421),不再推荐使用。它存在严重问题:执行时间不可控、性能开销大、易导致内存泄漏或死锁。取而代之的是 java.lang.ref.Cleaner —— 一种基于虚引用(PhantomReference)的轻量、可预测、线程安全的资源清理机制。
Cleaner 是什么,为什么比 finalize 更可靠
Cleaner 是一个静态工具类,内部维护一个守护线程(daemon thread)监听虚引用队列。当对象仅被虚引用持有时,GC 回收该对象后,会将对应虚引用入队,Cleaner 的清理线程随后调用你注册的清理动作(Cleaner.Cleanable)。关键优势:
- 不依赖对象的生命周期钩子,避免 finalize 的“复活”风险和执行不确定性
- 清理逻辑与业务对象解耦,对象可被及时回收,不会因清理慢拖慢 GC
- 支持显式注册/注销,可精准控制清理时机(比如提前释放资源)
- 线程安全,多线程环境下无需额外同步
如何用 Cleaner 替代 finalize:三步走
以管理一个需要关闭的文件句柄为例:
-
声明静态 Cleaner 实例(通常在类级别定义一次):
private static final Cleaner cleaner = Cleaner.create(); -
在构造时注册 Cleanable(绑定对象与清理动作):
private final Cleaner.Cleanable cleanable;
public ResourceHolder(FileDescriptor fd) {
this.fd = fd;
this.cleanable = cleaner.register(this, new CleanupAction(fd));
} -
定义清理动作(实现 Runnable 或使用 lambda):
private static class CleanupAction implements Runnable {
private final FileDescriptor fd;
CleanupAction(FileDescriptor fd) { this.fd = fd; }
public void run() { try { fd.close(); } catch (IOException ignored) {} }
}
或更简洁:
this.cleanable = cleaner.register(this, () -> { /* 关闭 fd */ });
必须注意的关键细节
用好 Cleaner 需避开几个典型陷阱:
立即学习“Java免费学习笔记(深入)”;
-
不要在清理动作中访问已可能被回收的对象字段:Cleanable 持有对目标对象的弱引用,run() 执行时对象可能已被 GC;所有需清理的资源(如 fd、buffer、socket)应作为参数传入 Cleanable 或封装进独立持有者(如上面的
CleanupAction) -
手动注销 Cleanable 可提升确定性:如果资源可明确释放(如调用
close()),应同时调用cleanable.clean(),避免等待 GC —— 这是 RAII 思想的 Java 实现方式 - Cleaner 实例建议复用:一个 Cleaner 可注册成千上万个 Cleanable,无需每个类都 new 一个;但不同模块间若清理策略差异大(如超时、优先级),可考虑隔离
- 不能替代 try-with-resources:Cleaner 是兜底机制,不是主要资源管理手段;I/O、锁等仍应优先用显式 close / unlock + try-finally / try-with-resources
一个完整可运行示例
模拟一个需要释放本地内存的类:
class NativeBuffer {private static final Cleaner cleaner = Cleaner.create();
private final long address;
private final Cleaner.Cleanable cleanable;
NativeBuffer(int size) {
this.address = unsafe.allocateMemory(size);
this.cleanable = cleaner.register(this, new ReleaseAction(address));
}
void close() {
cleanable.clean(); // 主动触发清理
}
private static class ReleaseAction implements Runnable {
private final long address;
ReleaseAction(long address) { this.address = address; }
public void run() { unsafe.freeMemory(address); }
}
}


















