zoukankan      html  css  js  c++  java
  • 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 的对象,对于新出现的那些对象,我们可以简单地认为它们全都是存活的,毕竟数量不是很多。

    今天,你学习了吗
  • 相关阅读:
    Educational Codeforces Round 67 D. Subarray Sorting
    2019 Multi-University Training Contest 5
    Educational Codeforces Round 69 (Rated for Div. 2) E. Culture Code
    Educational Codeforces Round 69 D. Yet Another Subarray Problem
    2019牛客暑期多校训练第六场
    Educational Codeforces Round 68 E. Count The Rectangles
    2019牛客多校第五场题解
    2019 Multi-University Training Contest 3
    2019 Multi-University Training Contest 2
    [模板] 三维偏序
  • 原文地址:https://www.cnblogs.com/Howinfun/p/14880806.html
Copyright © 2011-2022 走看看