Cleaner 是 finalize 的现代替代方案,基于虚引用和守护线程实现线程安全、可预测的堆外资源清理,避免了 finalize 的不可控性、死锁风险与废弃问题。

Java 中 Cleaner 是 finalize 的现代、线程安全、可预测的替代方案,专为清理堆外内存(如 ByteBuffer.allocateDirect()、Unsafe.allocateMemory())等无法被 GC 自动回收的资源而设计。它不依赖对象可达性语义,也不受 GC 调度不确定性影响,能更及时、可控地执行清理逻辑。
为什么 Cleaner 比 finalize 更安全可靠
finalize 存在严重缺陷:执行时机不可控、可能永远不执行、与 GC 强耦合、存在性能开销和死锁风险,且自 Java 9 起已标记为废弃(@Deprecated(forRemoval = true))。Cleaner 则基于虚引用(PhantomReference)和引用队列,由独立的守护线程异步轮询触发清理,不阻塞 GC,也不影响对象回收流程。
- 清理动作与对象生命周期解耦,即使对象长期存活(如被缓存),也不会延迟资源释放
- 支持显式注册/取消注册,可精准控制清理时机
- 清理逻辑运行在专用线程(
CleanerImpl后台线程),避免在 GC 线程中执行任意用户代码 - 无 finalize 那样的“复活”(resurrection)风险,对象一旦入队即不可达
用 Cleaner 管理 DirectByteBuffer 堆外内存
以 ByteBuffer.allocateDirect() 为例:其底层使用 Unsafe.allocateMemory() 分配堆外内存,通过 Cleaner 关联清理动作。你也可以手动复用该机制管理自定义堆外资源:
public class OffHeapResource {
private final long address;
private final Cleaner cleaner;
public OffHeapResource(long size) {
this.address = Unsafe.getUnsafe().allocateMemory(size);
this.cleaner = Cleaner.create();
this.cleaner.register(this, new ResourceCleaner(address));
}
private static class ResourceCleaner implements Runnable {
private final long address;
ResourceCleaner(long address) {
this.address = address;
}
@Override
public void run() {
if (address != 0) {
Unsafe.getUnsafe().freeMemory(address);
}
}
}
// 显式清理(可选,用于提前释放)
public void close() {
cleaner.clean(); // 触发 run()
}
}
注意:不要在 run() 中抛出异常或执行耗时操作;若需日志,建议用 System.err.println 或异步记录,避免阻塞 Cleaner 线程。
立即学习“Java免费学习笔记(深入)”;
关键使用要点与避坑提醒
-
必须持有 Cleaner 实例引用:若将
Cleaner声明为局部变量或未保存,它本身可能被回收,导致注册失效 -
避免在 Cleaner 回调中访问已不可达对象字段:因为此时对象可能已被 GC 清理,仅保留虚引用。所有必要信息(如地址、句柄)应在
Runnable构造时捕获并保存 -
慎用 Cleaner.create() 多次:每个实例启动一个后台线程。推荐复用单例 Cleaner(
Cleaner.create()是轻量的,但线程是共享的);JDK 内部也复用BufferCleaner单例 -
不适用于需要强顺序保证的场景:Cleaner 清理是异步的,不能确保在某个对象方法返回前完成。如需同步释放,仍应提供
close()并配合 try-with-resources
与 try-with-resources 结合实现双重保障
最佳实践是“显式关闭 + Cleaner 保底”:实现 AutoCloseable,在 close() 中主动释放,并在 Cleaner 中做兜底清理:
public class SafeOffHeapBuffer implements AutoCloseable {
private long address;
private final Cleaner.Cleanable cleanable;
public SafeOffHeapBuffer(int size) {
this.address = Unsafe.getUnsafe().allocateMemory(size);
this.cleanable = Cleaner.create().register(this, new Cleanup(address));
}
@Override
public void close() {
if (address != 0) {
Unsafe.getUnsafe().freeMemory(address);
address = 0;
}
cleanable.clean(); // 取消待清理任务,避免重复 free
}
private static class Cleanup implements Runnable {
private final long address;
Cleanup(long address) { this.address = address; }
public void run() {
if (address != 0) {
Unsafe.getUnsafe().freeMemory(address);
}
}
}
}
这样既满足明确资源管理契约,又防止因使用者遗漏 close() 导致内存泄漏。


















