源码
@Deprecated(since="9")
protected void finalize() throws Throwable { }
已被弃用, 如果在 finalize()
中使对象重新被引用("复活"对象),下次回收时不会再调用 finalize()
特性
- 调用时机:当垃圾回收器确定该对象不再被引用时,会在回收内存前调用
- 不确定性:无法预测何时会被调用,甚至可能永远不会被调用
- 执行保证:不保证一定会执行,JVM 可能在未调用
finalize()
的情况下退出 - 异常处理:可以抛出异常,但会被垃圾回收器忽略
性能影响:
- 包含
finalize()
的对象需要至少两次垃圾回收才能被清除 - 会显著增加垃圾回收的开销
- 替代方案(Java 9+):
- 资源释放是确定性的(在
close()
调用时立即执行) - 不依赖垃圾回收机制
- 更高效且可靠
// 使用 AutoCloseable
public class BetterResource implements AutoCloseable {
@Override
public void close() {
// 确定性的资源清理
}
}
//try-with-resources实现
try (Resource res = new Resource()) {
// 使用资源
}
// 以上经过编译后为
Resource res = new Resource();
try {
// 使用资源
} finally {
if (res != null) {
res.close();
}
}
//Cleaner
Cleaner cleaner = Cleaner.create();
MyResource resource = new MyResource();
cleaner.register(resource, resource::close);
System.gc()和finalize()
System.gc()
是一个建议(而非命令)JVM 执行垃圾回收的方法:
- 它只是向 JVM 发出垃圾回收的请求
- JVM 可以选择立即执行 GC,也可能忽略这个请求
- 不能保证调用后一定会立即回收对象
当以下条件满足时,finalize()
会被调用:
- 对象变成垃圾(没有任何引用指向它)
- 垃圾回收器准备回收该对象的内存
- 这是该对象第一次被标记为可回收(每个对象的
finalize()
最多被调用一次
注意事项
-
不保证一定执行:
- 即使调用
System.gc()
,JVM 也可能不执行 GC - 即使执行 GC,也可能不立即调用
finalize()
- 即使调用
-
最多调用一次:
- 一个对象的
finalize()
最多被调用一次 - 如果在
finalize()
中使对象重新被引用("复活"对象),下次回收时不会再调用finalize()
- 一个对象的
-
性能影响:
- 有
finalize()
的对象需要至少两次 GC 才能被真正回收 - 会显著延长对象生命周期,增加内存压力
- 有
为什么带有 finalize()
的对象需要至少两次 GC 才能被回收
在 Java 中,实现了 finalize()
方法的对象确实需要经过至少两次垃圾回收(GC)才能被完全回收,这是由 JVM 的特殊处理机制决定的。下面详细解释这个现象的原因和过程:
1. 垃圾回收的基本流程(针对有 finalize()
的对象)
第一次 GC:
- 标记阶段:GC 发现对象不可达(没有引用指向它)
- 特殊处理:发现对象有
finalize()
方法 - 放入 Finalizer 队列:对象不会被立即回收,而是被放入一个叫
Finalizer
的特殊队列 - Finalizer 线程:JVM 有一个低优先级的
Finalizer
线程会逐个处理这个队列中的对象
在 Finalizer 线程中:
- 调用该对象的
finalize()
方法 - 执行完成后,对象再次变成"普通"不可达对象
第二次 GC:
- 再次发现对象不可达
- 这次对象没有
finalize()
需要调用(每个对象的finalize()
只调用一次) - 真正回收内存
2. 为什么需要这样设计?
-
安全考虑:
- 给对象最后一次"自救"机会(在
finalize()
中重新建立引用) - 确保资源清理代码有机会执行
- 给对象最后一次"自救"机会(在
-
避免无限循环:
- 限制
finalize()
只调用一次,防止对象通过finalize()
反复"复活"
- 限制
-
分离关注点:
- 垃圾回收线程专注于内存回收
- Finalizer 线程专注于执行清理操作
3. 性能影响
这种机制会导致:
- 内存释放延迟:对象存活时间延长
- 内存压力增加:特别是大量需要 finalize 的对象
- GC 效率降低:需要多次扫描和处理
4. 示例说明
public class FinalizeDemo {
private static FinalizeDemo savedRef; // 用于"自救"的静态引用
@Override
protected void finalize() throws Throwable {
System.out.println("finalize() 被调用");
savedRef = this; // 对象"自救"
}
public static void main(String[] args) throws InterruptedException {
new FinalizeDemo(); // 创建匿名对象,立即不可达
System.gc();
Thread.sleep(1000); // 给Finalizer线程时间
if (savedRef != null) {
System.out.println("对象被救活了!");
} else {
System.out.println("对象已被回收");
}
savedRef = null; // 移除引用
System.gc(); // 第二次GC才能真正回收
}
}
5. 为什么被弃用?
正是由于这种复杂的两阶段回收机制带来的问题:
- 不可预测性:无法保证
finalize()
何时执行 - 性能损耗:延长对象生命周期,增加GC压力
- 潜在风险:不当的
finalize()
实现可能阻止对象回收
Java 9 开始推荐使用 java.lang.ref.Cleaner
和 PhantomReference
作为替代方案,它们提供了更高效和可控的对象清理机制。