前言
相对于引用计数算法而言,根搜索算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效的解决在引用记数法中一些已经死亡的对象因为相互引用而导致的无法正确被
标记的问题,防止内存泄漏的发生。
算法原理
根搜索算法是以根对象集合为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达(使用根算法后,内存中的存活对象都会被根对象集合直接或间接连接着)
如果目标对象不可达,就意味着该对象已经死亡,便可将其标记为垃圾对象。
在根搜索算法中不可达的对象,也并非是“非死不可”,这时候他们暂时处于“缓行”阶段,要真正宣告一个对象死亡,至少要经历2次标记过程。如果对象在进行根搜索后发现并没有与GC Roots相连接的
引用链,那么它将会被第一次标记并进行一次筛选,筛选的条件是这个对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,
虚拟机将这两种情况都视为没有必要执行finalize()方法。如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为F-Queue的队列之中,并在稍后有一条虚拟机自动建立、
低优先级的 finalize的线程去执行。虚拟机会触发finalize()方法,但并不承诺等待它执行结束。finalize()方法是对象逃脱死亡的最后一次F-Queue中的对象机会。GC将会对F-QUEUEF-Queue中的对
象进行第二次小规模的标记,如果某个对象重新与GC Roots引用链上的对象建立关联关系,那么第二次标记时它将被移除F-Queue。如果对象这个时候还没有逃脱,那他就真的离死不远了。
就拿上图来说,ObjectD和ObjectE是互相关联的,但是由于GC roots到这两个对象不可达,所以最终D和E还是会被当做GC的对象,上图若是采用引用计数法,则A-E五个对象都不会被回收。
逃脱回收实验
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive(){
System.out.println("i am still alive");
}
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalizeEscapeGC();
//对象第一次拯救自己
SAVE_HOOK = null;
System.gc();
//因为Finalize方法的优先级很低,暂停5秒
Thread.sleep(500);
if(SAVE_HOOK != null){
SAVE_HOOK.isAlive();
}else{
System.out.println("i am dead");
}
//以下代码与上面的完全相同,但是这次却自救失败了
SAVE_HOOK = null;
System.gc();
//因为Finalize方法的优先级很低,暂停5秒
Thread.sleep(500);
if(SAVE_HOOK != null){
SAVE_HOOK.isAlive();
}else{
System.out.println("i am dead");
}
}
}
控制台输出
finalize method executed
i am still alive
i am dead
从代码的运行结果看出,SAVE_HOOK对象的finalize()方法确实被GC收集器出发过,并且在被手机前成功逃脱了。另一个值得注意的地方是,代码中有2段完全一样的代码,执行结果是一次成功,一次失败,
这是因为热河一个对象的finalize()方法都会被系统自动调用一次,如果对象面临下一次回收,他的finalize()方法不会被再次执行,因此后面的方法执行自救失败了。