java 9起finalize()被弃用,推荐使用cleaner——基于虚引用的轻量级资源清理机制;它不阻塞gc、执行可预测、避免死锁,需静态声明cleaner、注册cleanable并确保清理动作幂等且不访问已回收对象字段。

Java 9 起,finalize() 已被标记为 @Deprecated,不再推荐用于资源清理。它不可靠、性能差、且无法保证执行时机或是否执行。取而代之的是 Cleaner —— 一个轻量、可预测、基于虚引用(PhantomReference)的清理机制,专为安全释放外部资源(如文件句柄、内存映射、本地内存、网络连接等)设计。
为什么 Cleaner 比 finalize 更可靠
Cleaner 不依赖 GC 的 finalize 阶段,而是绑定到对象的生命周期终点:当对象变为不可达且被 GC 回收时,Cleaner 会异步触发注册的清理动作。它不阻塞 GC,不拖慢对象回收,也不受 finalize 队列积压影响。更重要的是,Cleaner 清理逻辑由开发者显式控制,无隐式调用风险,也避免了 finalize 中常见的死锁或重入问题。
如何正确使用 Cleaner 管理外部资源
核心思路是:将资源持有者(如自定义类)与资源本身分离;用 Cleaner 关联“干净的”清理函数,确保即使对象异常终止也能释放底层资源。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 声明静态
Cleaner实例(通常为 class-level),避免重复创建开销 - 在资源持有类中持有一个“清理令牌”(通常是
Cleaner.Cleanable),它在构造时注册清理动作 - 清理动作应只操作外部资源(如
unsafe.freeMemory()、mappedByteBuffer.force()、close()),绝不访问已可能被回收的 Java 对象字段 - 显式调用
cleanable.clean()提前释放(如配合AutoCloseable.close()),避免等待 GC
示例(管理直接内存):
class UnsafeResource {private static final Cleaner CLEANER = Cleaner.create();
private final Cleanable cleanable;
private final long address;
UnsafeResource() {
this.address = UNSAFE.allocateMemory(1024);
this.cleanable = CLEANER.register(this, new ResourceCleanup(address));
}
void close() {
cleanable.clean(); // 主动清理
}
static class ResourceCleanup implements Runnable {
private final long address;
ResourceCleanup(long address) { this.address = address; }
public void run() { UNSAFE.freeMemory(address); }
}
}
关键注意事项和常见陷阱
Cleaner 行为高度依赖 JVM 实现细节,需严格遵循契约:
- 清理动作必须是幂等的——可能被多次调用(尤其在调试或 GC 压力下)
- 禁止在清理逻辑中抛出未捕获异常(会静默吞掉,且可能干扰 Cleaner 线程)
- 不要在
Runnable中访问当前对象的实例字段(对象此时已不可达,字段可能为 null 或任意值) - Cleaner 使用后台守护线程执行,但该线程不保证实时性;对时效性极高的资源(如数据库连接池中的连接),仍应优先走显式关闭流程
- 若资源本身实现了
AutoCloseable,Cleaner 应作为兜底保障,而非替代try-with-resources
对比:Cleaner vs PhantomReference 手动实现
Cleaner 本质是对 PhantomReference + 引用队列 + 后台线程的封装。手动实现虽更灵活,但易出错(如漏处理引用队列、线程泄漏、竞态)。Cleaner 内置了线程复用、异常屏蔽、批量清理优化,且 API 更简洁。除非有特殊调度需求(如绑定到特定线程池),否则直接使用 Cleaner.create() 即可。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










