对象回收前的两次标记

JVM

可达性分析算法中不可达的对象,也并不是一定会被回收掉,他们在真正的被回收掉之前,至少会经历两次标记过程:

如果对象在执行可达性分析后发现没有引用链跟GC Roots相连接,那他将会第一次被标记,并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法或者已经执行过一次finalize()方法,虚拟机都将这两种情况视为没有必要执行。

如果对象被视为有必要执行finalize()方法,这个对象会被放在一个叫做F-Queue的队列中,标在稍后有虚拟机创建的,低优先级的Finalizer线程中去执行它。这里的执行是指虚拟机只会触发这个方法,单并不保证会等待这个方法执行结束,之所以这样做不是因为如果这个对象的finalize()方法执行缓慢,或者发生了死循环,将会导致队列中的其他对象永久的等待,甚至导致整个垃圾回收系统崩溃。之后GC将会对F-Queue中的对象进行第二次的小规模的标记,如果对象在finalize()方法中,跟引用链上的任何一个对象建立了关联,第二次标记的时候他就会被移除即将被回收的集合,如果没有跟引用链上的任何一个对象建立关联,那么这个时候他就真的被回收了。

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
41
42
43
44
45
46
47
/**
* 此代码演示了两点:
* 1.对象可以在被GC时自我拯救。
* 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
* @author zzm
*/
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 mehtod executed!");
FinalizeEscapeGC.SAVE_HOOK = this;
}

public static void main(String[] args) throws Throwable {
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("no, i am dead :(");
}

// 下面这段代码与上面的完全相同,但是这次自救却失败了
SAVE_HOOK = null;
System.gc();
// 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no, i am dead :(");
}
}
}

之所以有一次拯救成功了,有一次失败了,是因为finalize()方法只能被执行一次。

并不推荐使用finalize()方法拯救对象,因为该方法代价高昂,不确定性大,关于有些地方提到的这个方法适合关闭外部资源的说法,则完全有些自我安慰,而且关闭外部资源完全可以用try-finally更好的实现。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器