finalize 方法不是保证执行的清理入口,而是 jvm 在对象回收前可能调用的不可靠、不及时、不保证顺序的回调钩子,java 9 起已弃用,推荐用 autocloseable + try-with-resources 或 cleaner 替代。

finalize 方法不是“保证会执行的清理入口”,而是 JVM 在对象即将被回收时**可能调用**的一个回调钩子,但它不可靠、不及时、不保证顺序,也不该作为主要资源管理手段。
finalize 的本质是“尽力而为”的回收前通知
当垃圾回收器(GC)扫描到某个对象已不可达(即没有任何活跃引用),且该对象重写了 finalize() 方法,JVM 会把这个对象加入一个叫“终结队列”(Finalization Queue)的特殊队列。稍后,由一个低优先级的 Finalizer 线程异步取出并调用它的 finalize()。这个过程:
- 不保证发生——如果程序运行结束前 GC 根本没触发,或对象一直被意外持有,finalize 就永远不会执行
- 不保证时机——可能延迟几秒、几分钟,甚至整个生命周期都不触发
- 不保证只调一次——JVM 明确规定:对每个对象最多调用一次,即使 finalize 内部又让对象“复活”(比如把 this 赋给静态变量),下次 GC 也不会再调
它曾经想解决的问题:非托管资源清理
Java 的 GC 只管堆内存释放,但像文件句柄、Socket、本地内存(JNI 分配)、图形上下文等,不属于 Java 堆,GC 不知道怎么关、怎么释放。早期设计者希望用 finalize 作为“兜底安全网”——万一开发者忘了手动 close,至少还有一次补救机会。
例如:
protected void finalize() throws Throwable {
if (fileStream != null) {
fileStream.close(); // 尝试关闭,避免句柄泄漏
}
super.finalize();
}
但现实是:依赖它等于赌运气。文件句柄有限,等 finalize 来关,很可能在触发前就抛出 IOException: Too many open files。
现代 Java 中,它已被明确弃用
从 Java 9 开始,finalize() 被标记为 @Deprecated;Java 18 进一步将其设为“废弃(deprecated for removal)”;未来版本将彻底删除。
替代方案清晰且强制:
- 显式关闭:实现 AutoCloseable,配合 try-with-resources(推荐)
- 清理接口:如 Cleaner(Java 9+),基于虚引用(PhantomReference)实现更可控、不阻塞 GC 的异步清理
- 作用域管理:用 try-finally 或封装类确保资源一定释放
初学者最容易踩的坑
别在 finalize 里做这些事:
- 等待锁、网络请求、线程 sleep —— 会卡住整个 Finalizer 线程,拖慢所有待终结对象
- 抛出未捕获异常 —— 异常会被吞掉,但该对象的终结就此终止,后续资源可能永久泄漏
- 试图“复活”对象(如赋值给 static 字段)—— 行为不可预测,且违背资源确定性释放原则
- 当成析构函数来用 —— 它不是 C++ 的 destructor,没有确定性语义










