Finalizer / FinalReference / finalize() GC 回调源码精读
概述
Object.finalize() 是 JDK 早期提供的"对象被回收前回调",底层由 Finalizer(一个 FinalReference 子类)与 FinalizerThread 守护线程驱动。它设计有缺陷(时序不确定、性能差、安全风险),JDK 9 起被标记 @Deprecated,推荐用 Cleaner 替代。理解它的实现,才能真正明白"为什么不推荐 finalize"。本文基于 OpenJDK 21 源码拆解。
一、Finalizer 的注册机制
java
// java.lang.ref.Finalizer
final class Finalizer extends FinalReference<Object> {
private static ReferenceQueue<Object> queue = new ReferenceQueue<>(); // 待执行队列
private static final Object lock = new Object();
private static Finalizer unfinalized = null; // 未回收对象的链表头
private Finalizer next, prev; // 链表指针
private boolean hasBeenFinalized; // 是否已执行 finalize
// 静态块启动 FinalizerThread 守护线程
static {
Thread t = new FinalizerThread(tg, "Finalizer");
t.setPriority(MAX_PRIORITY - 2); // 次高优先级
t.setDaemon(true);
t.start();
}
// 注册:对象创建时(JVM 在 new 带 finalize 的类时调用)
static void register(Object finalizee) {
new Finalizer(finalizee); // 构造时加入 unfinalized 链表
}
private Finalizer(Object finalizee) {
super(finalizee, queue); // FinalReference,关联执行队列
// 加入 unfinalized 链表(保证强可达,防止提前回收)
add();
}
}Finalizer 注册时机:
类含 finalize() → JVM 在 new 实例时自动调用 Finalizer.register(this)
(字节码层面:new 后插入 register 调用)
unfinalized 链表的作用:
让所有"待终结对象"被 Finalizer 强引用
→ 否则对象被回收时 Finalizer 也回收,finalize 无从执行二、FinalizerThread 守护线程
java
private static class FinalizerThread extends Thread {
public void run() {
while (true) {
// ① 从队列取待执行对象
Finalizer f = queue.poll(); // 非阻塞尝试
if (f == null) {
// ② 无 → 等待 GC 通知
Finalizer.lock.wait(); // 阻塞(GC 回收后 notify)
continue;
}
// ③ 执行 finalize
f.runFinalizer();
}
}
}FinalizerThread 运行模型:
守护线程(优先级仅次最高,确保优先执行)
GC 回收对象 → Finalizer 入队 → 通知
线程从队列取 Finalizer → runFinalizer 执行 finalize三、runFinalizer() 的执行细节
java
private void runFinalizer() {
synchronized (this) {
if (hasBeenFinalized) return; // ① 只执行一次
hasBeenFinalized = true; // 标记已执行
}
Object finalizee = this.get(); // ② 取回被回收的对象引用
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
// ③ 反射调用 finalize()(Object.finalize 是 protected,用反射)
finalizee.finalize(); // 可能抛异常(被吞掉,线程不终止)
/* 调用链实际为:
java.lang.reflect.Method m = finalizee.getClass().getDeclaredMethod("finalize");
m.invoke(finalizee); // 反射执行(缓存 Method 减少开销)*/
}
// ④ 清理引用链(允许对象被真正回收)
super.clear();
}runFinalizer 流程:
检查 hasBeenFinalized(保证只执行一次)
取回引用 → 反射调用 finalize()
异常被忽略(不影响线程继续)
最后 clear() → 断开引用 → 对象下一轮 GC 真正释放四、finalize() 只执行一次与二次回收
java
private boolean hasBeenFinalized; // 执行后置 true
// 生命周期:
// 第 1 轮 GC:对象不可达 → Finalizer 入队(对象还没被回收!被 Finalizer 引用)
// FinalizerThread:执行 finalize() → clear()
// 第 2 轮 GC:对象彻底不可达 → 内存真正释放为什么"两次 GC":
第 1 轮:GC 发现不可达 → Finalizer 入队,对象仍被 Finalizer 持有
finalize() 中对象可能"复活"(重新被赋值给外部引用)
第 2 轮:finalize 已执行、引用已 clear → 对象真正回收
注意:
对象在 finalize 中复活 → 第 2 轮不会回收,可继续存活
(复活对象再次不可达时,不会再执行 finalize —— 只执行一次)五、System.runFinalization()
java
public static void runFinalization() {
Runtime.getRuntime().runFinalization();
}
// java.lang.Runtime.runFinalization
public void runFinalization() {
// 尽力执行已入队但尚未执行的 finalize
// 只是"建议":执行线程可能忙于其他任务
Finalizer.runFinalization();
}runFinalization 语义:
主动触发:执行队列中积压的 finalize 任务
尽力而为:不保证全部执行(线程竞争、对象量不确定)
实际场景:内存紧张/退出前尝试清理资源,但不可依赖六、重写 finalize() 的性能代价
性能代价分析:
① 对象创建:JVM 检测含 finalize → 额外调用 Finalizer.register
→ 进入 unfinalized 链表(对象存活期间一直被引用)
② GC 处理:Finalizer 作为 FinalReference 参与可达性分析(额外开销)
③ 回收延迟:至少多一轮 GC(对象晚一个周期才释放)
④ 执行开销:守护线程 + 反射调用 finalize
⑤ 安全性:finalize 可复活对象 → GC 无法立即回收(内存泄漏风险)
结论:
finalize 使对象回收不可预测 → 吞吐下降、内存压力增大
JDK 9:Object.finalize 标记 @Deprecated(since = "9")
JDK 18:进一步标记 @Deprecated(forRemoval = true)(拟移除)java
// JDK 9+ 弃用标记
@Deprecated(since = "9", forRemoval = true) // JDK 18 起
protected void finalize() throws Throwable { }七、Cleaner vs Finalizer
| 维度 | Finalizer(finalize) | Cleaner |
|---|---|---|
| 引用类型 | FinalReference | PhantomReference |
| 执行线程 | 全局 FinalizerThread | 每个 Cleaner 自己的清理线程 |
| 用户回调 | 重写 finalize()(反射调用) | 注册 Runnable |
| 时序 | 对象未释放时执行(可复活) | 对象不可达后执行(无法复活) |
| 异常 | 吞掉 | 可控制 |
| 性能 | 两次 GC、反射、链表 | 一次入队 + 直接 run |
| 可控性 | 差(无取消/幂等保障) | Cleanable.clean() 可主动触发 |
| 状态 | @Deprecated(forRemoval=true) | 推荐替代方案 |
java
// Cleaner 替代写法(比 finalize 安全可控)
Cleaner cleaner = Cleaner.create();
Object resource = new Resource();
Cleaner.Cleanable cleanable = cleaner.register(resource, () -> {
// 资源清理逻辑(确定性执行)
});Cleaner 优势:
确定性:注册的 Runnable 明确且可手动触发
安全性:PhantomReference 无法复活对象
性能:少一轮 GC、无反射、无全局链表
可维护:清理逻辑与对象解耦(Runnable)八、实现要点
Finalizer 核心:
注册:JVM new 时调用 register → 加入 unfinalized 链表
执行:FinalizerThread(次高优先级守护线程)从队列取 → 反射 finalize
只执行一次:hasBeenFinalized 标记
两次 GC:先入队执行 finalize,clear 后下一轮才真正释放
runFinalization:尽力执行(不保证)
弃用:JDK 9 Deprecated,JDK 18 forRemoval
替代:Cleaner(PhantomReference + Runnable,更可控安全)
常见陷阱:
依赖 finalize 释放资源 → 时序不可控(文件句柄可能迟迟不释放)
finalize 复活对象 → 内存泄漏 + 行为诡异
异常在 finalize 中被吞 → 问题难排查
生产代码一律用 Cleaner / try-with-resources 替代