G1 记忆集

君子以自强不息。

简介

记忆集(Remember Set,简称 RSet),RSet 和卡表是为了记录对象在不同代际之间的引用关系,目的是在垃圾回收时能够快速地找到活跃对象,而不必遍历整个堆空间。

实现

JVM 使用根对象引用的收集算法,从根集合出发,标记所有存活的对象,然后遍历对象的每一个字段继续标记,直到所有的对象标记完毕。在分代 GC 中,我们知道新生代和老生代处于不同的收集阶段,如果还是按照这样的标记方法,既不合理也没必要。假设我们只收集新生代,我们把老生代全部标记,但是并没有收集老生代,浪费了时间。同理,在收集老生代时有同样的问题。当且仅当,我们要进行 Full GC 才需要做全部标记。所以算法设计者用一个RSet记录从非收集部分指向收集部分的指针的集合,而这个集合描述对象的引用关系。

引用关系

通常有两种方法记录引用关系,第一成为 Point Out,第二是 Point in。如 ObjA.Field=ObjB,对于 Point out 来说在 ObjA 的 RSet 中记录 ObjB 的地址,对于 Point in 来说在 ObjB 的 RSet 中记录 ObjA 的地方,这相当于一种反向引用。这两者的区别在于处理有所不同,Point Out 记录操作简单,但是需要对 RSet 做全部扫描;Point In 记录操作复杂,但是在标记扫描时直接可以找到有用和无用的对象,不需要额外的扫描,因为 RSet 里面的对象可以认为就是根对象。

分区引用

在 G1 中提供了3种收集算法。新生代回收、混合回收和 Full GC。新生代回收总是收集所有新生代分区,混合回收会收集所有的新生代分区以及部分老生代分区,而 Full GC 则是对所有的分区处理。

分区之间的引用关系(引用关系指分区里面有一个对象存在一个指针指向另一个分区的对象。)可以归纳为:

  • 分区内部有引用关系。
  • 新生代分区到新生代分区之间有引用关系。
  • 新生代分区到老生代分区之间有引用关系。
  • 老生代分区到新生代分区之间有引用关系。
  • 老生代分区到老生代分区之间有引用关系。

哪些引用关系会被记录到 RSet

  • 新生代分区到新生代分区之间有引用关系,这个无需记录,原因在于 G1 的这3种回收算法都会全量处理新生代分区,所以它们都会被遍历,所以无需记录新生代到新生代之间的引用。
  • 新生代分区到老生代分区之间有引用关系,这个无需记录,对于 G1 中 YGC 针对的新生代分区,无需这个引用关系,混合 GC 发生的时候,G1 会使用新生代分区作为根,那么遍历新生代分区的时候自然能找到老生代分区,所以也无需这个引用,对于 FGC 来说更无需这个引用关系,所有的分区都会被处理。
  • 老生代分区到新生代分区之间有引用关系,这个需要记录,在 YGC 的时候有两种根,一个就是栈空间/全局空间变量的引用,另外一个就是老生代分区到新生代分区的引用。
  • 老生代分区到老生代分区之间有引用关系,这个需要记录,在混合 GC 的时候可能只有部分分区被回收,所以必须记录引用关系,快速找到哪些对象是活跃的。

总结

引用关系 是否记录在 RSet
分区内部
新生代分区到新生代分区
新生代分区到老生代分区
老生代分区到新生代分区
老生代分区到老生代分区