我们应该都知道Java虚拟机在进行垃圾回收操作的时候,会先进行垃圾判定,会使用引用计数法和可达性算法来进行对象是否回收判断;
可达性算法的基本思路是通过”GC Roots“的对象作为起始点,从这些点开始往下搜索,搜索所走过的路径为引用链,当一个对象到“GC Roots”没有任何引用链相连,证明该对象是不可达的,即不可用,是可回收对象;
在Java中可以做GC Roots的对象包含以下几种:
1、虚拟机栈(栈帧中的本地变量表)中引用的对象;
2、方法区中的静态变量所引用的对象;
3、方法去中常量引用的对象;
4、本地方法栈中JNI(即Native方法)引用的对象;
但是可达性算法不可达的对象就一定会死亡,会被回收吗?
答案是否定的!
即使在可达性算法中不可达的对象也不一定是非死不可的,这时候它们暂时处于“缓刑”阶段,要真正宣告它的死亡还需要经历两次的标记阶段。
第一次标记:
在对象可达性算法不可达时,进行第一次标记,并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize方法或者该方法被虚拟机调用过,虚拟机将这两种情况视为“没有必要去执行”。
如果该对象被判定为有必要执行finalize()方法,那么这个对象会被放置到一个叫做F-Queue的队列中,并在稍后由一个虚拟机自动建立的、低优先级的Finalize线程去执行它。这里所谓的执行就是去触发该方法,但是并不会承诺等待它执行结束,这样做的原因是,如果对象在finalize()方法中执行缓慢,或者发生死循环,将会导致整个队列中的对象处于等待之中。
第二次标记:
finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中拯救自己——只要重新与引用链上的一个对象重新建立关联即可,譬如将自己(this关键字)赋值给某个类变量或者成员变量,那么在第二次标记的时候就会被移除“即将回收”的集合;如果对象这时候还没有逃脱,那么就会被真的回收了!
示例代码:
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive(){
System.out.println("yes, i am still alive!");
}
@Override
public 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方法优先级很低所以暂停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();
//因为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!
no, i am dead!
注意:第二次自救失败是因为任何一个对象的finalize()方法只能执行一次,如果第二次回收,就不会执行finalize方法了!