1. JVM G1 RSet记忆集 Remember Set


1.1. 标记一个对象的流程

标记一个对象时,需要通过gc roots追踪引用关系来标记,如果一个对象被gc roots引用或者被gc roots引用的对象引用,该对象为存活对象,否则为垃圾对象,然后就可以对垃圾对象进行回收了。

确定引用关系最简单的方式是,从gc roots 出发,一个个对象去遍历(把每一个对象的每个字段引用的内容都遍历到)就能找到引用关系,同时也把标记做好了。

jvm_g1_rset_01

上述方式会有遗漏,即跨代引用。例如老年代一个对象引用新生代,使用新生代的gc roots则不能找到。所以老年代的一些对象,也需要加入到新生代的gc roots里去遍历。


1.2. 跨代引用

G1也是有分代的,young gc针对的是整个新生代的空间,会选择所有新生代的region,根据gc roots遍历整个新生代。如果没有老年代对新生代的引用关系,则垃圾回收的时候就可能会出现误操作。

所以跨代引用关系在 GC 的时候必须知道。最简单的方式是,直接把老年代也遍历一边找引用关系。此时是新生代的回收,这样操作很不合适,同时效率也很低。

jvm_g1_rset_02


1.3. 记录跨代引用关系 的 RSet记忆集

G1的堆内存中有多块region,一部分属于新生代,一部分属于老年代。老年代的对象引用了新生代的对象,可以针对这种引用关系在一块内存上做存储(即存储谁引用了我)。

jvm_g1_rset_03

遍历新生代所有对象之前,要把这块内存里面的引用我的对象也加入到gc roots中,然后进行存活标记。这样就能避免遍历整个老年代了。从效率和分代隔离的层面,这样做是非常合理的。

jvm_g1_rset_04

对于G1,是以region为最小内存管理维度的,所以 RSet 记忆集的维度是针对每一个region的(每个region都都有一块内存作为 RSet),用来存储region中所有对象被引用的关系数据。

针对region维度,是因为每次回收后,老年代、新生代、大对象区域的region类别属性可能都会变化。所以,按分代作引用关系存储,不合理(因为region不断的在变化,同时也会有并发问题,效率问题)。

jvm_g1_rset_05

最终,G1选择使用RSet记忆集这种方式,通过记录的跨代引用关系,方便在新生代垃圾回收的时找到跨代gc roots,减少不必要的遍历,提升效率。


1.4. 在G1中的引用关系

引用关系种类如下:

  1. region分区内部的引用关系
  2. 新生代region 到 新生代region有引用关系
  3. 新生代region 到 老年代region有引用关系
  4. 老年代region 到 新生代region有引用关系
  5. 老年代region 到 老年代region有引用关系

如果不记录某种引用关系,会造成漏标记、漏回收的情况,则这种关系就必须要记录下来。引用关系的几种情况分析如下:

  1. region内部的引用关系

    不论新生代、老年代,还是full gc的回收,在gc roots标记的过程中,内部引用关系一定可以被追踪。回收时,分区内所有对象一定都会被遍历,所以内部的引用关系不需要记录。

  1. 新生代和新生代间的引用关系

    新生代之间的引用关系和region内部的原理一样,新生代gc的时候肯定要把所有新生代区域全部遍历,所以不需要记录。

  2. 新生代到老年代的引用关系

    1. 当新生代gc时,不会回收老年代内的对象,并且这种引用关系在新生代gc过程中,会被遍历到,故不需要纪录。

    2. 当老年代gc时,老年代region需要知道谁引用了我。gc回收过程老年代不会单独回收,老年代的gc肯定是会在新生代回收 或者 full gc的过程中发生,故不需要记录。

  3. 老年代到新生代的引用

    这种引用关系是需要记录的,最开始已经分析了这种情况。

  4. 老年代到老年代的引用

    当mixed回收时,此时老年代只会选择回收部分region。并不是所有region都会遍历,所以需要记录老年代到老年代的引用关系,避免在mixed回收时 遍历 整个老年代。

results matching ""

    No results matching ""