引入
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()
一次
代码分析
让我们逐步分析代码的执行流程和为什么第二次自救失败了:
- 初始化对象并第一次自救成功:
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
,对象成功自救。- 第二次尝试自救失败:
- 再次将
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()
方法存在几个问题:
-
不确定性:开发者无法控制
finalize()
方法何时被调用,甚至无法确定它是否会被调用。这可能导致资源泄露或程序行为不一致。 -
性能问题:
finalize()
方法的调用会增加垃圾回收的复杂性和开销,因为它需要 JVM 跟踪所有需要调用finalize()
方法的对象。 -
安全性问题:
finalize()
方法可以被重写,这可能导致安全问题,因为恶意代码可能会重写该方法以执行不受欢迎的操作。 -
替代方案:现代 Java 提供了更好的资源管理机制,如 try-with-resources 语句和显式的 close 方法(实现了 AutoCloseable 或 Closeable 接口的对象),这些机制更加可靠和易于管理。