vlambda博客
学习文章列表

阅读小笔记:G1垃圾回收器

1、为啥需要G1

在 G1 出来之前,一般系统都是使用 ParNew + CMS。而不管是 ParNew 还是 CMS,对于新生代和老年代都是使用满了再进行gc,那么如果我们的机器配置了60G的内存,新生代和老年代的比例是1:2,那么老年代可以去到40G,那么进行垃圾回收时,即使CMS的最后一个阶段是并发清理,但是由于内存很大,那么对几十个g的内存进行回收,还是会耗费非常长的时间,并发清理阶段,即使GC线程和系统线程并行,但是由于GC线程会长时间进行垃圾回收,那么就会长时间占用系统资源,导致系统无法处理更多的用户请求。

即:ParNew + CMS无法做到软实时,即无法做到将GC的停顿大致控制在某个阈值以内。而在 G1 垃圾回收器中,我们可以利用参数-XX: MaxGCPauseMilllis设置垃圾收集器最大停顿时间

2、GC重要概念:Region & Card & Remember Set

在 JVM 中,堆一般被分为 Eden、两Survivor和老年代,在Java进程启动时,每个区的内存是固定分配好的。

而在 G1 中,最核心的三个概念是:Region、Card 和 Remember Set。

2.1 Region

1、所谓 Card 就是表示一小块(512 bytes)的内存空间,这里面很可能存在不止一个对象 2、默认将堆内存分成2048个Region

要特别注意的是,巨型对象(Humongous Object) ,即大小超过 3/4 的 Region 大小的对象会作特殊处理,分配到由一个或多个连续 Region 构成的区域。

2.2 Remember Set (RSet)

每个 Region 会有自己对应的 Remember Set,主要是记录哪些内存区域中存在对当前 Region 中对象的引用。

但是这已经足够了:当我们需要确定当前 Region 有哪些对象存在外部引用时(这些对象是可达的,不能被回收),只要扫描一下这块 Card 中的所有对象即可,这比扫描所有存活对象要容易得多。

3、G1的分代

G1 会从逻辑上将 Region 分成 Young、Old 等不同的分代。

在经典的内存布局中,各代的内存区域是完全分开的,而 G1 中的分代只是 Region 的一个动态标志。

各个 Region 的所属的分代是随着 GC 的进行而不断变化的,甚至各个代有多少 Region 这个比例也是随时调整的。

分代容量的JVM参数

-XX:G1NewSizePercent:设置新生代初始占比的。

默认5%

-XX:G1MaxNewSizePercent:设置新生代最大占比

在系统运行中,JVM其实会不停的给新生代增加更多的Region,但是最多新生代的占比不会超过60%(默认值)

设置Eden和Survivor占比还是以前的参数:-XX:SurvivorRatio

4、G1中的GC

分代模式下的G1垃圾回收分为两种:Young gc 和 Mixed GC。

上面提到,我们可以利用参数来设定期望的GC停顿时长,G1 是利用 Collection Set(CSet) 这个概念,G1 会根据配置的-XX: MaxGCPauseMilllis参数来控制 CSet 的大小,CSet 会控制存放可回收的 Region 数量。

在进行垃圾回收时,Young Regions 一定会被放到待收集的 Regions 集合(Collection Set)中,因为新生代中的对象大部分都是寿命比较短的。由于 Young Regions 一定会被收集,所以 RSet 的维护工作不需要考虑新生代中对象的引用修改,只关心 old-to-young 和 old-to-old 的引用),当 Young Region 上发生垃圾时我们再去扫描并构建出它的 RSet 即可。

4.1 Young GC

Young GC 只会涉及到新生代的N个 Region,它将 Eden Region 中存活的对象移动到一个或多个新分配的 Survivor Region,之前的 Eden Region 就被归还到 Free list,供以后的新对象分配使用。

当区域中对象的 Survive 次数超过阈值(参数:-XX:MaxTenuringThreshold)时,Survivor Regions 的对象被移动到 Old Regions;否则和 Eden 的对象一样,继续留在 Survivor Regions 里。

多次 Young GC 之后,Old Regions 慢慢累积,直到到达阈值(-XX:InitiatingHeapOccupancyPercent,简称 IHOP,默认45%),我们不得不对 Old Regions 做收集。这个阈值在 G1 中是根据用户设定的 GC 停顿时间动态调整的,也可以人为干预。

4.2 Mixed GC

对 Old Regions 的收集会同时涉及若干个 Young 和 Old Regions,因此被称为 Mixed GC 。

Mixed GC 的重要性不言而喻:Old Regions 的垃圾就是在这个阶段被收集掉的,也正是因为这样,Mixed GC 是工作量最为繁重的一个环节,如果不加以控制,就会像 CMS 一样发生长时间的 Full GC 停顿。

那来不及收集的那些 Region 呢?多来几次就可以了。所以你在 GC 日志中会看到 continue mixed GCs 的字样,代表分批进行的各次收集。这个过程会多次重复,直到垃圾的百分比降到 -XX:G1HeapWastePercent 以内,或者到达-xx:G1MixedGCCountTarget上限。

5、并发标记原理

在进行垃圾回收之前,G1 要通过并发标记来确定哪些对象是垃圾、哪些还活着。G1 中的并发标记阶段是以 Region 为单位的,为了保证结果的正确性,这里用到了 Snapshot-at-the-beginning(SATB)算法。

SATB 算法顾名思义是对标记开始时的一个(逻辑上的)快照进行标记。

基于 cur 指针实现并发标记

最后一个问题是:如何处理 Concurrent Marking 中新产生的对象?因为 SATB 算法只保证能标记到开始时 snapshot 的对象,对于新出现的那些对象,我们可以简单地认为它们全都是存活的,毕竟数量不是很多。