java对象被GC宣告死亡之前至少要经历两次标记过程。
概述
在java对象被GC宣告死亡之前至少要经历两次标记过程,如果对象没有与GC Roots相连的引用链(这里使用的算法是可达性分析算法),第二次就是是否调用对象的finalize(),如果调用了,并且在finalize()方法里面对象重新获取了GC Roots的引用,那么对象依旧可以存活下去,否则将会被清除。
1.第一次标记
当JVM触发一次 minor GC(这里具体指的是针对java堆新生代的垃圾回收,因为在有的虚拟机里有可能对方法区进行垃圾回收)时,如果对象在此时没有与GC Roots的引用链,那么对象就会第一次被标记,可作为GC Roots大概如下几种:
1.虚拟机栈引用的对象。 2.方法区中静态属性引用的对象。 3.方法区中常量引用的对象。 4.本地方法栈中Native方法引用的对象。 5.JVM内部引用,如基本数据类型对应的Class对象,一些常驻的异常对象,还有系统类加载器。 6.所以被同步锁(synchronized关键字)持有的对象。 7.反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
2.第二次标记
在第二次标记的时候,对象只有一次机会可以逃脱被清除的命运,就是程序员重写了finalized()方法、并且是这个对象第一次触发GC,并且finalized()方法内部为这个对象重新获取了GC Roots的引用。所以如果想要一个被GC标记了的对象不被清除,可以重写当前对象的finalized()方法(注意:只有第一次触发GC才会调用此方法,第二次不会调用)
演示代码如下(代码为《深入理解java虚拟机》代码清单3-2)
/**
* 此代码演示了两点:
* 1.对象可以在被GC时自我拯救。
* 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
*
* @author zzm
*/
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();
// 因为Finalizer方法优先级很低,暂停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();
// 因为Finalizer方法优先级很低,暂停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 :(
本文参考了周志明大大的《深入理解java虚拟机》第三版,特此说明