Object的finalize()方法

55 阅读4分钟

源码

@Deprecated(since="9")
protected void finalize() throws Throwable { }

已被弃用, 如果在 finalize() 中使对象重新被引用("复活"对象),下次回收时不会再调用 finalize()

特性

  1. 调用时机:当垃圾回收器确定该对象不再被引用时,会在回收内存前调用
  2. 不确定性:无法预测何时会被调用,甚至可能永远不会被调用
  3. 执行保证:不保证一定会执行,JVM 可能在未调用 finalize() 的情况下退出
  4. 异常处理:可以抛出异常,但会被垃圾回收器忽略

性能影响:

  • 包含 finalize() 的对象需要至少两次垃圾回收才能被清除
  • 会显著增加垃圾回收的开销
  1. 替代方案(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() 会被调用:

  1. 对象变成垃圾(没有任何引用指向它)
  2. 垃圾回收器准备回收该对象的内存
  3. 这是该对象第一次被标记为可回收(每个对象的 finalize() 最多被调用一次

注意事项

  1. 不保证一定执行

    • 即使调用 System.gc(),JVM 也可能不执行 GC
    • 即使执行 GC,也可能不立即调用 finalize()
  2. 最多调用一次

    • 一个对象的 finalize() 最多被调用一次
    • 如果在 finalize() 中使对象重新被引用("复活"对象),下次回收时不会再调用 finalize()
  3. 性能影响

    • 有 finalize() 的对象需要至少两次 GC 才能被真正回收
    • 会显著延长对象生命周期,增加内存压力

为什么带有 finalize() 的对象需要至少两次 GC 才能被回收

在 Java 中,实现了 finalize() 方法的对象确实需要经过至少两次垃圾回收(GC)才能被完全回收,这是由 JVM 的特殊处理机制决定的。下面详细解释这个现象的原因和过程:

1. 垃圾回收的基本流程(针对有 finalize() 的对象)

第一次 GC:

  1. 标记阶段:GC 发现对象不可达(没有引用指向它)
  2. 特殊处理:发现对象有 finalize() 方法
  3. 放入 Finalizer 队列:对象不会被立即回收,而是被放入一个叫 Finalizer 的特殊队列
  4. Finalizer 线程:JVM 有一个低优先级的 Finalizer 线程会逐个处理这个队列中的对象
在 Finalizer 线程中:
  1. 调用该对象的 finalize() 方法
  2. 执行完成后,对象再次变成"普通"不可达对象

第二次 GC:

  1. 再次发现对象不可达
  2. 这次对象没有 finalize() 需要调用(每个对象的 finalize() 只调用一次)
  3. 真正回收内存

2. 为什么需要这样设计?

  1. 安全考虑

    • 给对象最后一次"自救"机会(在 finalize() 中重新建立引用)
    • 确保资源清理代码有机会执行
  2. 避免无限循环

    • 限制 finalize() 只调用一次,防止对象通过 finalize() 反复"复活"
  3. 分离关注点

    • 垃圾回收线程专注于内存回收
    • 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. 为什么被弃用?

正是由于这种复杂的两阶段回收机制带来的问题:

  1. 不可预测性:无法保证 finalize() 何时执行
  2. 性能损耗:延长对象生命周期,增加GC压力
  3. 潜在风险:不当的 finalize() 实现可能阻止对象回收

Java 9 开始推荐使用 java.lang.ref.CleanerPhantomReference 作为替代方案,它们提供了更高效和可控的对象清理机制。

image.png