finalize()的局限性

引入

finalize() 方法是 Java 中 Object 类的一个方法,它可以在垃圾回收器决定销毁对象之前被调用。这个方法的主要用途是清理资源,比如关闭文件流、释放网络连接等。然而,需要注意的是,finalize() 方法并不保证会被调用,也不保证何时被调用,甚至不保证只被调用一次。

 问题发现

在了解JVM的垃圾 收集时看到如下这段代码,该代码含义是Java中的对象自救(self-rescue)机制,特别是在对象被垃圾回收器(GC)回收之前通过finalize()方法进行的尝试,同时也让我看到finalize()的局限性,一个对象只能被finalize()一次

代码如下(带详解)

public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;
    public void isAlive() {
        System.out.println("yes, i am still alive :)");
    }
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }
    public static void main(String[] args) throws Throwable {
        //SAVE_HOOK = new FinalizeEscapeGC(); 创建一个FinalizeEscapeGC对象,并将其引用赋给SAVE_HOOK。
        SAVE_HOOK = new FinalizeEscapeGC();
//对象第一次成功拯救自己
        //SAVE_HOOK = null; 将SAVE_HOOK置为null,使得该对象成为垃圾回收的候选。
        SAVE_HOOK = null;
        //调用System.gc(); 建议JVM执行垃圾回收。
        System.gc();
// 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
        //等待一段时间(Thread.sleep(500);),以确保垃圾回收器有机会运行。
        Thread.sleep(500);
        //垃圾回收器运行时,发现SAVE_HOOK引用的对象是可回收的,
        // 于是调用该对象的finalize()方法。在finalize()方法中,对象通过将自身重新赋值给SAVE_HOOK来自救。
        //因此,当检查SAVE_HOOK时,它不为null,对象成功自救。
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead :(");
        }
// 下面这段代码与上面的完全相同,但是这次自救却失败了
        SAVE_HOOK = null;
        System.gc();
// 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
        Thread.sleep(500);
        //垃圾回收器尝试回收对象,但由于该对象之前已经被回收过(并且其finalize()方法已经被调用过),
        // Java规范明确指出,一旦对象的finalize()方法被调用过,JVM将忽略后续对该对象的任何finalize()调用请求。
        // 因此,尽管对象再次成为垃圾回收的候选,但finalize()方法不会被再次调用,对象也就无法自救。
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead :(");
        }
    }
}

运行结果

finalize method executed!
yes, i am still alive :)
no, i am dead :(

我们可以看到最后结果,第一次自救成功第二次尝试自救失败,让我看到finalize()的局限性,一个对象只能被finalize()一次

代码分析

让我们逐步分析代码的执行流程和为什么第二次自救失败了:

  1. 初始化对象并第一次自救成功
    • SAVE_HOOK = new FinalizeEscapeGC(); 创建一个FinalizeEscapeGC对象,并将其引用赋给SAVE_HOOK
    • SAVE_HOOK = null; 将SAVE_HOOK置为null,使得该对象成为垃圾回收的候选。
    • 调用System.gc(); 建议JVM执行垃圾回收。
    • 等待一段时间(Thread.sleep(500);),以确保垃圾回收器有机会运行。
    • 垃圾回收器运行时,发现SAVE_HOOK引用的对象是可回收的,于是调用该对象的finalize()方法。在finalize()方法中,对象通过将自身重新赋值给SAVE_HOOK来自救。
    • 因此,当检查SAVE_HOOK时,它不为null,对象成功自救。
  2. 第二次尝试自救失败
    • 再次将SAVE_HOOK置为null,尝试重复上述过程。
    • 调用System.gc(); 和 Thread.sleep(500);
    • 垃圾回收器尝试回收对象,但由于该对象之前已经被回收过(并且其finalize()方法已经被调用过),Java规范明确指出,一旦对象的finalize()方法被调用过,JVM将忽略后续对该对象的任何finalize()调用请求。因此,尽管对象再次成为垃圾回收的候选,但finalize()方法不会被再次调用,对象也就无法自救。
    • 检查SAVE_HOOK时,它自然是null,因此输出“no, i am dead :(”。

finalize() 方法存在问题

从 Java 9 开始,finalize() 方法被标记为 @Deprecated,意味着它已经被弃用,并建议开发者避免使用。这是因为 finalize() 方法存在几个问题:

  1. 不确定性:开发者无法控制 finalize() 方法何时被调用,甚至无法确定它是否会被调用。这可能导致资源泄露或程序行为不一致。

  2. 性能问题finalize() 方法的调用会增加垃圾回收的复杂性和开销,因为它需要 JVM 跟踪所有需要调用 finalize() 方法的对象。

  3. 安全性问题finalize() 方法可以被重写,这可能导致安全问题,因为恶意代码可能会重写该方法以执行不受欢迎的操作。

  4. 替代方案:现代 Java 提供了更好的资源管理机制,如 try-with-resources 语句和显式的 close 方法(实现了 AutoCloseable 或 Closeable 接口的对象),这些机制更加可靠和易于管理。

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值