垃圾收集算法

垃圾收集算法可以划分为“引用计数式垃圾收集 ”(Reference Counting GC)和“追踪式垃圾收集”(Tracing GC).

这两类也常被称为“直接垃圾收集”和“间接垃圾收集”。

分代收集理论

当前商业虚拟机的垃圾收集器,大多数遵循了“分代收集”(Generational Collection)的理论进行设计。

是一套符合大多数程序运行实际情况的经验法则,建立在两个分代假说之上:

  • 1) 弱分代假说(Weak Generational Hypothesis):绝多大数对象都是朝生夕灭。
  • 2) 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。

这两个假说奠定了多款常用的垃圾收集器一致的设计原则:

收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄分配到不同的区域中存储。

在具体的Java虚拟机中,一般都是将堆分为

  • 新生代(Young Generation
  • 老年代(Old Generation

一些名词

名词 含义
Minor GC / Young GC 新生代收集 指目标只是新生代的垃圾收集
Major GC / Old GC 老年代收集 指目标只有老年代的垃圾收集,目前只有CMS收集器
会有单纯收集老年代的行为。
有时Major GC也会表示整堆收集
Mixed GC 混合收集 指目标是收集整个新生代以及部分老年代的垃圾收集。
目目前只有G1收集器有这种行为。
Full GC 整堆收集 收集整个Java堆和方法区的垃圾收集

跨代引用假说

问题

对象不是独立的,对象之间会存在跨代引用。新生代中的对象有可能被老年代引用,反之亦然。

导致的问题就是:假设进行Minor GC,那么除了通过固定的GC Roots,还需要遍历所有老年代的对象

来保证可达性分析的正确性。理论上可行,只是有很大性能问题。

跨代引用假说

这时就有第三条经验法则:

  • 3) 跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极少数。

    隐含推论:存在引用关系的两个对象应该是倾向于同时存在或同时消亡的。

    如果某个新生代的对象存在跨代引用,由于老年代对象难以消亡,该引用会使得

    新生代对象在收集是同时得以存活,多次GC后同样也会晋升至老年代中。

    跨代引用就消除了。

对应解决方式

引入记忆集(Remembered Set),在新生代上建立一个全局的称为记忆集的数据结构,

这个结构把老年代划分为若干个小块,标识出老年代哪一块内存会存在跨代引用。

当发生Minor GC时,只有包含了跨代引用的小块内存中的老年代对象才会加入到

GC Roots中。

疑问

记忆集只标识了老年代的对象引用新生代对象的信息,

回收老年代时,如何确定老年代的对象没被新生代引用?

只回收老年代的行为目前只有CMS收集器才会有,其他收集器不存在只收集老年代的行为。

所以其他收集器收集新生代的时候就知道老年代对象有没有被引用了。

那么CMS是怎么做的?

CMS发生Old GC时,要把整个新生代作为GC Roots来进行扫描。

回收老年代的次数并不会很频繁,维护两份卡表也需要再耗一部分性能。

感觉是个经过权衡之后的做法。