Cleaner 和 PhantomReference 是 JVM 垃圾回收驱动的资源泄露探测机制,不依赖 finally 执行,能在对象不可达时触发清理回调以暴露未释放资源。

Java 中的资源泄露探测,不能只靠 finally 块是否写了——更关键的是:它是否真的执行了?比如线程被强制中断、JVM 异常退出、或 System.exit() 调用时,finally 可能根本没机会运行。这时,Cleaner 和 PhantomReference 就成了最后一道防线:它们不依赖正常控制流,而是依托 JVM 的垃圾回收机制,在对象不可达时触发清理回调,从而暴露“本该被释放却始终未释放”的资源。
为什么 Cleaner 比 finalize() 更可靠
finalize() 已被弃用,不仅执行时机不确定、可能永不调用,还会拖慢 GC 且引发死锁风险。Cleaner 是 JDK 9+ 提供的替代方案,基于虚引用(PhantomReference)实现,具备明确的注册/清理语义和可控的清理线程调度:
- 清理逻辑在独立的守护线程中异步执行,不阻塞 GC 线程
- 每个
Cleaner实例可绑定一个清理动作(Runnable),资源对象仅需注册即可 - 一旦对象被 GC 回收,且其
Cleaner尚未 clean,回调就会触发——若未触发,说明对象还活着,极可能泄漏
用 Cleaner 捕获未关闭的文件句柄
以 FileInputStream 为例,可在构造时注册 Cleaner,监控其是否被显式关闭:
public class TrackedFileInputStream extends FileInputStream {
private static final Cleaner cleaner = Cleaner.create();
private final Cleanable cleanable;
public TrackedFileInputStream(String name) throws FileNotFoundException {
super(name);
// 注册清理动作:仅当 close() 未被调用时才报警
this.cleanable = cleaner.register(
this,
new FileCleanupAction(getChannel().getFD())
);
}
@Override
public void close() throws IOException {
super.close();
cleanable.clean(); // 主动清理,避免误报
}
private static class FileCleanupAction implements Runnable {
private final FileDescriptor fd;
FileCleanupAction(FileDescriptor fd) { this.fd = fd; }
@Override
public void run() {
if (!fd.valid()) return;
// 记录日志或上报:发现未关闭的 fd!
System.err.println("⚠️ FileDescriptor leaked: " + fd);
}
}
}
这样,只要对象被 GC 但 close() 没调用,Cleaner 就会打印告警——比等 HandleCount 涨到几千再排查快得多。
PhantomReference 的精细控制场景
当你需要区分“对象已不可达”和“GC 已完成回收”两个阶段,或要配合引用队列做批量清理时,直接使用 PhantomReference 更灵活:
- 必须搭配
ReferenceQueue使用,GC 后引用入队,由你轮询处理 - 适合资源生命周期复杂的情况,例如数据库连接池中,连接被归还前需验证状态,而归还失败时靠 PhantomReference 触发兜底回收
- 注意:不能在
run()中复活对象,也不能访问其字段(此时对象已“死亡”,仅剩引用句柄)
实战建议:结合监控与基线对比
Cleaner 和 PhantomReference 不是万能补丁,而是诊断探针。实际落地要注意:
- 只对关键资源(如文件、Socket、DirectByteBuffer)启用,避免过度注册影响 GC 性能
- 清理动作务必轻量,禁止 I/O 或锁操作,否则可能卡住 Cleaner 线程
- 将 Cleaner 触发次数接入监控系统(如 Micrometer + Prometheus),设置阈值告警:1 小时内超过 5 次触发即视为高风险
- 配合
Process.HandleCount或jcmd <pid> VM.native_memory summary验证——如果 Cleaner 频繁报警,但句柄数稳定,可能是误报;若两者同步上涨,则基本坐实泄漏

















