一、Finalize方法的过程
一个对象真正宣告死亡,至少要经历两次标记过程:如果对象在进行可达性分析之后发现没有与GC Roots相连接的引用链,那它将被第一次标记并且进行一次筛选。筛选的条件是此对象是否有必要执行finalize()方法。
当遇到以下两种情况时,虚拟机将视为“没有必要执行”:
1.对象是否覆盖finalize()方法
2.finalize()方法已经被虚拟机调用过
如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个F-Queue的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程执行。
注:这里的“执行”指的是虚拟机有机会触发这个方法,但并不承诺会等待它运行结束。因为一个对象在finalize()方法中执行是非常缓慢的,甚至有可能会发生死循环,将会导致F-Queue队列中其他对象永久处于等待,甚至导致整个内存回收系统崩溃。
finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象在finalize()方法中拯救自己(重新与引用链上任何一个对象建立关联),那么在第二次标记时它将被移除“即将回收”的集合,如果对象这时候还没有逃脱,那基本上就真的被回收了~~~(gg
二、一次对象的自我拯救代码清单
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();
//对象第一次成功拯救自己
SAVE_HOOK = null;
System.gc();
//因为finalize方法优先级很低,所以暂停0.5s等待
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no,I am dead :(");
}
//第二次拯救失败
SAVE_HOOK = null;
System.gc();
//因为finalize方法优先级很低,所以暂停0.5s等待
Thread.sleep(500);
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 :(
从代码清单中可知,SAVE_HOOK对象的finalize()方法确实被GC收集器触发过,并且在被收集前成功逃脱了。
另外,在完全一样的两端代码片段里,第二次的执行结果确实逃脱失败了。这是因为任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法就不会被再次执行。
最后,在JVM中并不鼓励使用finalize()对象来拯救对象。因此它的运行代码非常高昂而且不确定性大。finalize()方法能做的工作,使用try-finally或者其他方式都可以做的更好更及时。