垃圾标记阶段:对象存活判断
我们清楚在堆中存放着几乎所有的Java对象实例,在GC执行垃圾回收之前,首先需要区分出内存中哪些是存活对象,哪些是已经死亡的对象,只有被标记为已经死亡的对象,GC才会在执行垃圾回收时,释放掉其占用的内存空间,因此这个过程我们可以称为垃圾标记阶段。
那么在JVM中究竟是如何标记一个死亡对象?简单来说,当一个对象已经不再被任何的存活对象继续引用时,就可以宣判为已经死亡。判断对象存货一般有两种方式:引用计数算法和可达性分析算法。
引用计数算法
引用计数算法比较简单,对每一个对象保存一个整性的引用计数器属性,用于记录对象被引用的情况。对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1;当引用失效时,引用计数器就减1,只要对象A的引用计数器的值为0,即表示对象A不可能再被使用,可进行回收。
优点:实现简单,垃圾对象便于标识,判定效率高,回收没有延迟性
缺点:
1.它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
2.每次赋值都需要更新计数器,伴随着加法和减法操作,增加了时间开销。
3.引用计数器有一个严重问题,即无法处理循环引用的情况,如下图所示,这是一条致命缺陷,导致在Java的来集会收器中没有使用这类算法。
验证Java中不适用引用计数算法
public class RefCountGC {
//这个成员属性唯一的作用就是占用一点内存
private byte[] bigSize = new byte[5 * 1024 * 1024];//5MB
Object reference = null;
public static void main(String[] args) {
RefCountGC obj1 = new RefCountGC();
RefCountGC obj2 = new RefCountGC();
obj1.reference = obj2;
obj2.reference = obj1;
obj1 = null;
obj2 = null;
//显式的执行垃圾回收行为
//这里发生GC,obj1和obj2能否被回收?
System.gc();
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面代码的图示如下
运行程序时,参数设置中增加参数-XX:+PrintGCDetails,会发现会进行GC,并且把上述的两个循环引用对象进行了回收。