finalize() 专用于释放 jni 分配的非堆内存,不可依赖于文件等资源清理,java 9 起弃用,应改用 cleaner 和 phantomreference。

Java 中 finalize() 方法处理非堆内存资源,核心逻辑是:它不是为常规资源清理设计的,而是专用于释放 JVM 无法自动管理的、由本地代码(如 JNI)分配的非堆内存(如 C 的 malloc 分配的内存)。其他非堆资源(如文件句柄、Socket、数据库连接等)应优先通过显式关闭机制管理,finalize() 仅可作为“兜底补充”,且不可依赖。
以下分三方面说明关键要点:
finalize 处理非堆内存资源的适用场景
只在以下情况才考虑使用 finalize():
- 对象内部通过 JNI 调用了 C/C++ 代码,并用
malloc、calloc等分配了 native 内存; - 这部分内存不会被 JVM 垃圾回收器追踪和释放;
- 必须在 Java 对象被回收前,通过 JNI 调用对应的
free()或释放函数; - 示例:
public class NativeResource { private long nativePtr; // 指向 C 分配的内存地址 public NativeResource() { nativePtr = allocateNativeMemory(); // JNI 方法,调用 malloc } @Override protected void finalize() throws Throwable { if (nativePtr != 0) { freeNativeMemory(nativePtr); // JNI 方法,调用 free nativePtr = 0; } super.finalize(); } private native long allocateNativeMemory(); private native void freeNativeMemory(long ptr); }
非堆资源(如文件、Socket)不推荐靠 finalize 清理
因为:
-
finalize()执行时间不确定,可能延迟数秒甚至数分钟,导致文件句柄长期占用、端口被锁死; - GC 可能永不触发(尤其内存充足时),资源实际未释放;
- 若
finalize()抛异常且未捕获,该次清理失败且无提示; - 正确做法是:
- 使用
try-with-resources(实现AutoCloseable); - 或手动调用
close()/shutdown()等明确释放方法; -
finalize()中最多只做一次性日志告警或调用已封装好的关闭逻辑(如this.close()),而非替代它。
- 使用
finalize 的执行机制与风险限制
- 它不是析构函数:C++ 中析构总在对象销毁时同步执行;Java 中
finalize()可能永不执行、延迟执行、甚至执行多次(若对象在finalize()中重新被引用); - JVM 不保证调用:只要内存未紧张,GC 可能长期不运行,
finalize()就不会触发; - 性能开销大:含
finalize()的对象会被放入特殊队列,经历两次 GC 才能真正回收; - Java 9 开始已标记为 deprecated,Java 18 后彻底移除(由
Cleaner和PhantomReference替代)。
不复杂但容易忽略:非堆内存资源清理,本质是 bridging native and Java lifecycles —— finalize() 是历史遗留的桥梁,现在应改用 Cleaner 配合 PhantomReference 实现可控、异步、可注册的清理逻辑。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











