2021-06-13:G1垃圾回收器

原文链接 2021-06-13: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

G1 垃圾收集器将堆内存空间分成等分的 Region,物理上不一定连续,逻辑上构成连续的堆地址空间;一个 Region 由多个 Card 组成,一个 Card 可以存放多个对象。

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

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

2.2 Remember Set (RSet)

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

注意 Remember Set 不是直接记录对象地址,而是记录了那些对象所在的 Card 编号。

但是这已经足够了:当我们需要确定当前 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 算法顾名思义是对标记开始时的一个(逻辑上的)快照进行标记。

开启并发标记后,由于是基于快照做的;所以期间如果需要修改引用,会记录引用地址,防止会漏掉。

标记的过程和 CMS 中是类似的,可以看作一个优化版的 DFS:记当前已经标记到的 offset 为 cur,随着标记的进行 cur 不断向后推进。每当访问到地址 < cur 的对象,就对它做深度扫描,递归标记所有应用;反之,对于地址 > cur 的对象,只标记不扫描,等到 cur 推进到那边的时候再去做扫描。

基于 cur 指针实现并发标记

基于 cur 指针实现并发标记
上图中,假设当前 cur 指向对象 c,c有两个引用:a 和 e,其中 a 的地址小于 cur,因而做了扫描;而 e 则仅仅是标记。扫描 a 的过程中又发现了对象 b,b 同样被标记并继续扫描。但是 b 引用的 d 在 cur 之后,所以 d 仅仅是被标记,不再继续扫描。

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

今天,你学习了吗
原文地址:https://www.cnblogs.com/Howinfun/p/14880806.html