Object.finalize() 是 Java 中最具争议且被误解的方法之一。作为 Java 垃圾回收机制的一部分,它在历史上被用于资源清理,但现代 Java 开发中已被弃用。本文将全面解析该方法。

核心概念

1. 方法定义

protected void finalize() throws Throwable

2. 设计目的

  • 资源清理:在对象被垃圾回收前执行清理操作
  • 最后机会:对象被销毁前的"临终遗言"
  • 安全网:作为资源释放的备用机制

3. 生命周期位置

对象创建 → 使用 → [不可达] → finalize() → 垃圾回收

工作机制

垃圾回收流程

  1. JVM 标记不可达对象
  2. 检查对象是否覆盖了 finalize() 方法
  3. 若有覆盖,将对象放入 Finalizer Queue
  4. Finalizer 线程(低优先级守护线程)执行队列中的 finalize() 方法
  5. 下一次 GC 时真正回收对象

关键特性

public class ResourceHolder {
    private FileInputStream stream;
    
    public ResourceHolder(String file) throws FileNotFoundException {
        stream = new FileInputStream(file);
    }
    
    // 不推荐使用的finalize示例
    @Override
    protected void finalize() throws Throwable {
        try {
            if (stream != null) {
                stream.close();
                System.out.println("Resource closed in finalizer");
            }
        } finally {
            super.finalize(); // 调用Object.finalize()
        }
    }
}

为什么被弃用(Java 9+)

1. 性能问题

  • 延迟回收:对象至少经历两次GC才能回收
  • 队列阻塞:Finalizer线程可能被慢速finalize阻塞
  • 内存泄漏风险:对象在队列中长时间滞留

2. 可靠性问题

  • 执行时间不确定:可能永远不被调用
  • 异常处理缺陷:finalize中的异常会被忽略
  • 顺序问题:无法保证依赖对象的清理顺序

3. 安全问题

  • 对象复活(Resurrection)漏洞:
    public class Zombie {
        static Zombie resurrected;
    
        @Override
        protected void finalize() {
            resurrected = this; // 对象复活!
        }
    }
    

替代方案(现代Java实践)

1. try-with-resources (Java 7+)

try (FileInputStream fis = new FileInputStream("file.txt");
     FileOutputStream fos = new FileOutputStream("output.txt")) {
    // 使用资源
} // 自动调用close()

2. Cleaner API (Java 9+)

import java.lang.ref.Cleaner;

public class ResourceHolder implements AutoCloseable {
    private static final Cleaner cleaner = Cleaner.create();
    
    private final FileInputStream stream;
    private final Cleaner.Cleanable cleanable;
    
    public ResourceHolder(String file) throws FileNotFoundException {
        stream = new FileInputStream(file);
        cleanable = cleaner.register(this, new ResourceCleaner(stream));
    }
    
    @Override
    public void close() {
        cleanable.clean();
    }
    
    // 静态内部类,不持有外部类引用
    private static class ResourceCleaner implements Runnable {
        private final FileInputStream stream;
        
        ResourceCleaner(FileInputStream stream) {
            this.stream = stream;
        }
        
        @Override
        public void run() {
            try {
                if (stream != null) {
                    stream.close();
                    System.out.println("Resource cleaned");
                }
            } catch (IOException e) {
                // 处理异常
            }
        }
    }
}

3. Phantom References (幻象引用)

public class PhantomResourceCleaner {
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
    private final Set<ResourceReference> references = new HashSet<>();
    
    public PhantomResourceCleaner() {
        // 启动清理线程
        new Thread(this::cleanup).start();
    }
    
    public void register(Object resource, Runnable cleanup) {
        references.add(new ResourceReference(resource, queue, cleanup));
    }
    
    private void cleanup() {
        while (true) {
            try {
                ResourceReference ref = (ResourceReference) queue.remove();
                ref.cleanup.run();
                references.remove(ref);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
    
    private static class ResourceReference extends PhantomReference<Object> {
        private final Runnable cleanup;
        
        ResourceReference(Object referent, ReferenceQueue<? super Object> q, 
                         Runnable cleanup) {
            super(referent, q);
            this.cleanup = cleanup;
        }
    }
}

历史最佳实践(仅适用于旧版本)

1. 正确实现模式

@Override
protected void finalize() throws Throwable {
    try {
        // 清理资源
        releaseResources();
    } finally {
        // 确保父类finalize被调用
        super.finalize();
    }
}

2. 安全注意事项

  • 避免对象复活:不要在finalize中保存对象引用
  • 幂等设计:多次调用finalize应安全
  • 异常处理:捕获所有异常,防止Finalizer线程终止
  • 同步控制:避免死锁,finalize可能在任何线程执行

3. 资源释放顺序

public class DependentResource {
    private Resource primary;
    private Resource secondary;
    
    @Override
    protected void finalize() throws Throwable {
        try {
            // 先释放依赖资源
            if (secondary != null) secondary.release();
        } finally {
            try {
                // 再释放主资源
                if (primary != null) primary.release();
            } finally {
                super.finalize();
            }
        }
    }
}

性能影响对比

方法 回收延迟 CPU开销 内存开销 可靠性
finalize()
Cleaner API
try-with-res
Phantom Refs

真实世界问题案例

案例1:JDK 中的 Finalizer 问题

早期 FileInputStreamFileOutputStream 使用 finalize 关闭文件描述符,导致:

  • 文件描述符耗尽
  • 长时间运行应用性能下降
  • 资源泄漏难以诊断

解决方案:JDK 9 改用 Cleaner 实现

案例2:Android 内存泄漏

public class BitmapWrapper {
    private Bitmap bitmap;
    
    public BitmapWrapper(Bitmap bitmap) {
        this.bitmap = bitmap;
    }
    
    @Override
    protected void finalize() throws Throwable {
        // 异步回收可能导致上下文错误
        bitmap.recycle();
        super.finalize();
    }
}

问题:finalize 在任意线程执行,可能导致 UI 线程问题

迁移指南(从 finalize 到现代替代)

步骤1:识别使用 finalize 的类

# 使用FindBugs或SpotBugs检测
findbugs -textui -onlyAnalyze .*MyClass.* target/classes

步骤2:评估资源类型

资源类型 推荐替代方案
文件/网络连接 try-with-resources
原生资源 Cleaner API
复杂对象图 PhantomReference + Queue
第三方资源 显式 close() 方法

步骤3:重构示例

重构前

public class LegacyResource {
    private NativeHandle handle;
    
    public LegacyResource() {
        handle = allocateNative();
    }
    
    @Override
    protected void finalize() throws Throwable {
        releaseNative(handle);
        super.finalize();
    }
}

重构后

public class ModernResource implements AutoCloseable {
    private static final Cleaner CLEANER = Cleaner.create();
    private final NativeHandle handle;
    private final Cleaner.Cleanable cleanable;
    
    public ModernResource() {
        handle = allocateNative();
        cleanable = CLEANER.register(this, () -> releaseNative(handle));
    }
    
    @Override
    public void close() {
        cleanable.clean();
    }
}

Java 版本演进

Java 版本 finalize 状态 重要变更
Java 1.0 引入 初始实现
Java 7 不推荐用于资源管理 引入 try-with-resources
Java 9 标记为 deprecated 引入 Cleaner API
Java 16 默认禁用 Finalization JEP 397: 密封类
Java 18 Finalization 正式移除 JEP 421: 弃用 Finalization

最佳实践总结

  1. 绝对不要在新代码中使用 finalize()

  2. 优先使用 try-with-resources 管理资源

  3. 对于原生资源,使用 Cleaner API

  4. 实现 AutoCloseable 接口提供显式关闭

  5. 使用静态分析工具检测遗留 finalize 用法

  6. 监控 Finalizer 队列

    // 检查Finalizer队列长度
    Class<?> clazz = Class.forName("java.lang.ref.Finalizer");
    Field queue = clazz.getDeclaredField("queue");
    queue.setAccessible(true);
    ReferenceQueue<Object> refQueue = (ReferenceQueue) queue.get(null);
    // 监控队列长度...
    
  7. 在性能关键系统中禁用 finalization

    # JVM 参数
    --finalization=disabled
    

结论

Object.finalize() 曾是 Java 资源管理的早期解决方案,但其设计缺陷导致:

  • 不可预测的执行时机
  • 严重的性能问题
  • 资源管理不可靠

现代 Java 开发中: ✅ 使用 try-with-resources 管理可关闭资源 ✅ 使用 Cleaner API 管理原生资源 ✅ 实现 AutoCloseable 提供确定性清理

在维护遗留系统时,如果必须使用 finalize:

  1. 保持实现简单可靠
  2. 避免对象复活
  3. 捕获所有异常
  4. 尽快迁移到现代替代方案

Java 平台已明确弃用并逐步移除 finalization 机制,开发者应积极采用现代资源管理技术。