在垃圾回收过程中,关于对象何时被回收一直是一个不能确切回答的问题,虚拟机在处理待回收对象时,首要问题是判定对象的生死,主流的java虚拟机都是采用的可达性分析算法来判定对象的生死,当然更广为人知的是一种叫做引用计数算法的方式(但是由于它在无法处理循环引用的问题,所以极少在虚拟机中被运用,但也有成功案例,比如微软的COM、FlashPlayer)。
在可达性分析算法中,具体来看其实是有两次标记才能判定对象死亡:第一次标记就是对象在与GC Roots是不可达的,这里需要进行一次标记并且筛选是否进入F-Queue队列(判定对象是否有必要执行finalize()
方法,如果对象没有重写该方法或者虚拟机已经调用过该方法表明没有必要再执行finalize()
方法了,邹否则全部拉到F-Queue队列,由虚拟机自动创建低优先级的线程去执行这个对象的finalize()
方法,但是虚拟机不保证该队列一定会执行结束,因为可能遇到某个或某些对象的finalize()
方法发生执行缓慢或者死循环的情况);第二次标记发生在第一次标记过且有必要执行finalize()
方法的对象,此时是对象唯一逃出去(不被回收)的机会,通过与任意对象建立关联即可,这时对象将会被移出“将被回收”的队列。System.gc()
方法是通知垃圾收集器该收垃圾了,但是虚拟机可不是你的小李子,说一句就执行依据的,我们在手动调用这个命令后,每种虚拟机都会自己的策略,在HotSpot中,反正是不能确定垃圾收集器何时才能真正工作的,但是对象的finalize()
方法必定只是执行一次的,这点很重要,下面是书中一个对象关于生存和死亡的逃逸过程:
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive() {
System.out.println("yes, I am still alive :) -- " + SAVE_HOOK);
}
//重写finalize方法,该方法只被调用一次,但并不是调用后立刻被回收
@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 InterruptedException {
SAVE_HOOK = new FinalizeEscapeGC();
/*
拯救成功
*/
SAVE_HOOK = null;
//提醒虚拟机进行垃圾回收,但是虚拟机具体什么时候进行回收就不知道了
System.gc();
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.5秒
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 :) -- com.hhu.FinalizeEscapeGC@1761e840
No, I am dead :(
第一次逃脱成功,原因在于对象重写了finalize()
方法,在我们手动调用System.gc()
时是出发了垃圾回收,在执行finalize()
方时, 在其中将 SAVE_HOOK
重新用this
关键字挂上和当前对象关系,所以在第二次标记时他已经不再“待回收”的队列中了,所以此时对象还是存活的;但是第二次逃亡的时候,不再执行了finalize()
方法了(之前执行过一次,对象的finalize()
方法必定只执行一次),在SAVE_HOOK
至为null
后不再可达,finalize()
方法也是没有必要执行的情况,所以它就直接为null
了,没有指向任何对象,此时对象已死。