垃圾标记阶段
- 对象存活判断:在堆里存放着几乎所有的Java对象实例,在GC执行垃圾回收之前,首先需要区分出内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为己经死亡的对象,GC才会在执行垃圾回收时,释放掉其所占用的内存空间,因此这个过程我们可以称为垃圾标记阶段。
- 那么在JVM中究竟是如何标记一个死亡对象呢?简单来说,当一个对象已经不再被任何的存活对象继续引用时,就可以宣判为已经死亡。
- 判断对象存活一般有两种方式:引用计数算法和可达性分析算法。
引用计数法 (java没有采用)
- 引用计数算法(Reference Counting)比较简单,对每个对象保存一个整型 的引用计数器属性。用于记录对象被引用的情况。
- 对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1;当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,即表示对象A不可能再被使用,可进行回收。
- 优点:实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟性。
- 缺点:
- ➢它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
- ➢每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。
- ➢引用计数器有一个严重的问题,即无法处理循环引用的情况。这是一 条致命缺陷,导致在Java的垃圾回收器中没有使用这类算法。
循环引用导致的内存泄漏情况:
证明:java使用的不是引用计数算法 示例代码:/** * -XX:+PrintGCDetails * 证明:java使用的不是引用计数算法 */ public class RefCountGC { //这个成员属性唯一的作用就是占用一点内存 private byte[] bigSize = new byte[5 * 1024 * 1024];//5MB,仅仅意味着只要创建我这个RefCountGC对象实例,就会占用5M的堆空间 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能否被回收?能,证明Java没有采用引用计数算法 System.gc(); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } } }
引用计数法的弊端:即使把obj1 一reference和obj2 一reference(栈引用)置null。 则在Java堆当中的两块内存依然保持着互相引用,无法回收。
但是把obj1 一reference和obj2 一reference置为null之后,如果显示的去调用System.gc();这里将会发生GC,回收堆中bj1和obj2实体。从而证明Jlava没有采用引用计数算法 .