即使在可达性分析算法中被判定为不可达的对象,也不是“非死不可”的,还有一种自救的方法。

要宣告一个对象已死,至少要经历两次标记过程:在被可达性分析算法判定为不可达后会被标记一次;随后会对不可达对象进行筛选,筛选条件是该对象是否有必要执行finalize()方法,如果对象没有重写finalize()方法,或者finalize()在上一次垃圾回收时被调用过,则该对象没有必要执行finalize()方法,会被第二次标记。对象即被判定为了“死亡”。

如果对象被判定为有必要执行finalize()方法,虚拟机会将该对象放置在一个名为F-Queue的队列之中,并在稍后由一个由虚拟机自动创建的、低调度优先级的Finalizer线程去执行它们的finalize()方法。这里的执行是虚拟机会触发该方法的开始执行,但不会等待它们执行结束。原因是该方法的方法体是经过重写的,无法保证立刻执行结束(等待或死循环的情况),很可能造成F-Queue队列中的其它对象永远处于等待状态,导致整个垃圾回收系统崩溃。

finalize()方法是对象逃离死亡的最后一次机会,触发执行后,垃圾收集器将对F-Queue队列中的对象进行第二次小规模标记,如果对象要在finalize()方法中拯救自己——只需重新与引用链上的任何一个对象建立关联即可,例如将自身(this)赋值给某个类变量或者某个对象的成员变量,那在下一次垃圾回收时该对象将被移出“即将回收”的集合;如果该对象此时还未逃脱,那基本可以说它已经“死了”。

对象自救代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class FinalizeEscapeGc {
public static FinalizeEscapeGc SAVE_HOOK = null;

public void isAlive() {
System.out.println("alive");
}

@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize");
// 将this赋值给当前类的静态变量进行自救
FinalizeEscapeGc.SAVE_HOOK = this;
}

public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalizeEscapeGc();
// 第一次会进行自救
SAVE_HOOK = null;
System.gc();
// Finalizer线程优先级较低,暂停0.5秒以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("dead");
}

// 第二次被判定为“死亡”
SAVE_HOOK = null;
System.gc();
// Finalizer线程优先级较低,暂停0.5秒以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("dead");
}
}
}

输出结果:

1
2
3
finalize
alive
dead

可看到finalize()方法被执行了,且第一次“自救成功”,第二次则彻底“死亡”。

参考

  • 《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》 - 周志明