在可达性分析算法中不可达的对象,并不是一定要回收的。真正判定一个不可达对象要回收,至少要经历两次标记过程。
第一次标记
如果对象在进行可达性分析之后发现没有与 GC Roots 相连接的引用链,那它将会被第一次标记并且进行一次筛选。
筛选的条件是此对象是否需要执行 finalize() 方法。当对象没有覆写 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,这两种情况都是不需要执行 finalize() 方法。
如果对象被判定为需要执行 finalize() 方法,这个对象就会被放在一个叫做 F-Queue 的队列之中,并在稍后由一个虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。这里的执行是指虚拟机会触发这个方法,但不一定会等待它运行结束。这是因为如果一个对象在 finalize() 方法中执行缓慢,或者发生死循环,将可能导致 F-Queue 队列中其他对象永久处于等待,甚至导致整个内存回收系统崩溃。
第二次标记
finalize() 方法是对象拯救自己的最后一次机会。
GC 会对 F-Queue 中的对象进行第二次标记,如果对象在finalize()方法中 将自己与引用链的任何一个对象建立关联, 那么在第二次标记时它将会被移出 即将回收的集合。否则将会被回收。
示例
如下,对象第一次成功拯救自己,第二次只能死亡。。。。
package finalize;
public class FinalizeEscapeGC {
static FinalizeEscapeGC finalizeEscapeGC=null;
@Override
protected void finalize() throws Throwable {
System.out.println("拯救自己...........................");
//将自己与引用链关联起来
finalizeEscapeGC=this;
}
public void isLive(String name){
System.out.println(name+": 对象还活着.........");
}
public static void main(String []args) throws Exception{
finalizeEscapeGC=new FinalizeEscapeGC();
//让对象可以被回收
finalizeEscapeGC=null;
System.gc();
//finalize 方法优先级很低,所以暂停 0.5 秒以等待它
Thread.sleep(500);
if (finalizeEscapeGC==null){
System.out.println("第一次 对象死了。。。。。。。。。");
}else {
finalizeEscapeGC.isLive("第一次 ");
}
//让对象可以被回收
finalizeEscapeGC=null;
System.gc();
//finalize 方法优先级很低,所以暂停 0.5 秒以等待它
Thread.sleep(500);
if (finalizeEscapeGC==null){
System.out.println("第二次 对象死了。。。。。。。。。");
}else {
finalizeEscapeGC.isLive("第二次 ");
}
}
}
执行结果输出如下:
可以看出,GC之后,finalize() 方法触发,与引用链关联,成功执行自己,第二次却没有触发 finalize() 方法。
这是因为任何一个对象的 finalize() 方法都只会被系统自动调用一次,第二次垃圾回收时 finalize() 方法将不会执行。
参考资料
学习摘抄于深入理解Java虚拟机