G1垃圾回收器详解

news2024/9/20 20:34:38

文章目录

  • 前言
  • 一、思考问题
  • 二、官方文档
  • 三、基本介绍
  • 四、G1的内存模型
  • 五、G1的标记过程
  • 六、G1的垃圾回收
    • 1、G1过程梳理
    • 2、Young GC
    • 3、Mixed GC
    • 4、Full GC
  • 七、参数介绍
  • 八、典型问题
    • 1、疏散失败(Evacuation Failure)
    • 2、大对象分配(Humongous Allocation)
    • 3、Young GC花费时间太长
    • 4、Mixed GC耗时太长
  • 总结


前言

本来不准备写关于G1垃圾回收器的文章,因为网上介绍的文章真的太多了,写出来容易千篇一律,有抄袭的嫌疑。
但由于最近工作中遇到了G1垃圾回收期的线上优化问题,查找了很多资料,最终还是决定做一个总结,也希望能对大家有所帮助。


一、思考问题

先抛出一些关于G1垃圾回收器的问题,如果你都能回答上来,说明真的吃透了G1垃圾回收器,那么这篇文章你可以跳过了。如果还存在疑问,希望本文能给你解惑。

  1. G1有哪些特点?
  2. G1是分区还是分代?
  3. G1的一个内存单元Region中可以同时包含年轻代和老年代吗?
  4. G1相比CMS有哪些优点?相比ZGC有什么缺点?
  5. G1配置过程中,有哪些重要参数和注意事项?
  6. G1的使用过程中,遇到过哪些问题,怎么解决的?
  7. G1怎么实现目标暂停时间的?
  8. G1垃圾回收的过程中哪些阶段会出现STW?
  9. G1中哪些情况对象会被转移到老年代Old Genetion?
  10. G1中触发GC的时机?
  11. G1有哪些缺点?
  12. G1的适用场景?
  13. G1能否支持上T的大内存?
  14. G1在哪些情况下会出现Full GC?

二、官方文档

官网:https://www.oracle.com/technical-resources/articles/java/g1gc.html

java8下的g1说明
java19下的g1说明
G1垃圾收集器详解

注意
推荐使用的是什么版本的JDK就查看对应版本的官网文档说明,因为不同版本间的一些参数可能会有些细微的不同。由于目前主流还是使用java8,所以本文主要基于java8来对g1垃圾回收器展开介绍。

三、基本介绍

G1(Garbage First)垃圾收集器是当今垃圾回收技术最前沿的成果之一。早在JDK7就已加入JVM的收集器大家庭中,成为HotSpot重点发展的垃圾回收技术,JDK9 默认就是使用的G1垃圾收集器。

G1其实是Garbage First的意思,垃圾优先? 不是,是优先处理那些垃圾多的内存块的意思。

Garbage-First (G1)垃圾收集器是一个服务器风格的垃圾收集器,针对具有大内存的多处理器机器,它试图在实现高吞吐量的同时,以较高的概率满足垃圾收集(GC)暂停时间目标。

G1中堆被划分为一组大小相等的堆区域,每个区域有一个连续的虚拟内存范围。G1执行并发全局标记阶段,以确定整个堆中对象的活性。在标记阶段完成后,G1知道哪些区域大部分是空的。它首先收集这些区域,这往往会产生大量的自由空间。这就是为什么这种垃圾收集方法被称为“垃圾优先”
顾名思义,G1将其收集和压缩活动集中在堆中可能充满可回收对象(即垃圾)的区域。G1使用暂停预测模型来满足用户定义的暂停时间目标,并根据指定的暂停时间目标选择要收集的区域数

G1将对象从堆的一个或多个区域复制到堆的单个区域,并在进程中压缩和释放内存。这种疏散是在多处理器上并行执行的,以减少暂停时间并提高吞吐量。因此,对于每次垃圾收集,G1都会持续地减少碎片

需要注意的是,G1并不是一个实时收集器。它满足设定的暂停时间目标,具有较高的概率,但不是绝对确定的。根据以前收集的数据,G1估计在目标时间内可以收集多少个区域。因此,收集器拥有一个相当精确的区域收集成本模型,并使用该模型来确定在暂停时间目标内收集哪些区域以及收集多少个区域。

G1的第一个重点是为运行需要大堆且 GC 延迟有限的应用程序的用户提供解决方案。这意味着堆大小约为6 GB 或更大,并且稳定且可预测的暂停时间低于0.5秒
(注意:在Java19推荐10G或者更大的堆内存)

如果应用程序具有以下一个或多个特性,那么当今使用 CMS 或并行压缩运行的应用程序将从切换到 G1中受益。

  • 堆内存超过6G且活跃的数据超过java堆内存的50%
  • 对象分配和晋升的速度非常快
  • 应用程序不希望垃圾回收或内存压缩的暂停时间超过0.5s到1s

G1和CMS有哪些区别?
G1被计划作为并发标记扫描收集器(CMS)的长期替代品,通过比较 G1和 CMS 的差异,可以帮助我们更好的理解G1,并合理的选用。

1、空间压缩:G1采用复制-整理算法,在压缩空间方面有优势,可以避免产生内存空间碎片,而CMS采用标记-清除算法,会产生较多的空间碎片
2、暂停时间的可控性:G1使用暂停预测模型来满足用户定义的暂停时间目标,并根据指定的暂停时间目标选择要收集的区域数。CMS无法设置目标暂停时间,暂停时间不可控。
3、内存模型方面:G1采用物理分区,逻辑分代,Eden,Survivor,Old区不在是连续的一整块内存,而是又不连续的内存区域Region组成。而CMS中Eden,Survivor,Old区是连续的一整块内存。
4、G1既可以收集年轻代,也可以收集老年代,而CMS只能在老年代使用。

G1和ZGC比较?
最核心的问题是G1未能解决复制-转移过程中准确定位对象地址的问题,无法做到复制-转移过程的并行。

G1的优缺点
优点:
1、支持较大的内存
2、暂停时间可控
3、压缩空间,避免产生内存碎片
4、简单配置就能达到很好的性能

缺点:
1、记忆集RSet会占用比较大的内存,因此不建议在小内存下使用G1,推荐至少6G
2、对CPU的负载可能会更大一点
3、由于采用复制算法,GC垃圾回收过程对象复制转移会占用较多的内存,更容易出现回收失败(Allocation (Evacuation) Failure)的问题。
4、可能会降低吞吐量
虽然 G1收集器的垃圾收集暂停时间通常要短得多,但应用程序吞吐量也往往略低一些。相当于把一次垃圾回收的工作,分开多次进行执行(主要指老年代),单次暂停的时间虽然更加可控,但是由于每次垃圾回收的空间会更少,总体来说垃圾回收的效率会更低,暂停的总时间会更长,所以吞吐量往往会略低一些。

四、G1的内存模型

G1 是一个既分区也分代的垃圾收集器,这意味着 Java 对象堆(堆)被划分为许多大小相同的区域。在启动时,Java 虚拟机(JVM)设置区域大小。根据堆大小,区域大小可以从1MB 到32MB 不等。目标是不超过2048个地区。伊甸园、幸存者和老一代是这些区域的逻辑集合,并不相邻。

由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制。G1 跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

G1的内存模型:
在这里插入图片描述
说明:
G1是物理分区,逻辑分代

  • 红色区域是年轻代( young generation),包含伊甸区(eden regions,红色不带S区域)和幸存区(survivor regions ,红色带S区域)
  • 浅蓝色区域是老年代(old generation),其中包含跨多个区域组成的大对象区域(Humongous Region,蓝色带H区域)
  • 灰色区域表示空闲区(free regions)

传统的GC内存模型:
传统的垃圾回收器把内存分成三类: Eden(E), Suvivor(S)和Old(O)。 Eden(E), Suvivor(S)属于年轻代,Old(O)属于老年代,且各区域的内存空间是连续的。
在这里插入图片描述

针对G1的内存模型中的补充说明:

  • 巨型对象Humongous Region
    一个大小达到甚至超过分区大小一半的对象称为巨型对象(Humongous Object)。当线程为巨型对象分配空间时,不能简单在TLAB进行分配,因为巨型对象的移动成本很高,而且有可能一个分区不能容纳巨型对象。因此,巨型对象会直接在老年代分配,所占用的连续空间称为巨型分区(Humongous Region)
    G1内部做了一个优化,一旦发现没有引用指向巨型对象,则可直接在年轻代收集周期中被回收。
    巨型对象会独占一个、或多个连续分区,其中第一个分区被标记为开始巨型(Starts Humongous),相邻连续分区被标记为连续巨型(Continues Humongous)。由于无法享受Lab带来的优化,并且确定一片连续的内存空间需要扫描整堆,因此确定巨型对象开始位置的成本非常高,如果可以,应用程序应避免生成巨型对象。

  • 记忆集合Remember Set (RSet)
    在串行和并行收集器中,GC通过整堆扫描,来确定对象是否处于可达路径中。然而G1为了避免STW式的整堆扫描,在每个分区记录了一个已记忆集合(RSet),内部类似一个反向指针,记录引用分区内对象的卡片索引。当要回收该分区时,通过扫描分区的RSet,来确定引用本分区内的对象是否存活,进而确定本分区内的对象存活情况。事实上,并非所有的引用都需要记录在RSet中,如果一个分区确定需要扫描,那么无需RSet也可以无遗漏的得到引用关系。那么引用源自本分区的对象,当然不用落入RSet中;同时,G1 GC每次都会对年轻代进行整体收集,因此引用源自年轻代的对象,也不需要在RSet中记录。最后只有老年代的分区可能会有RSet记录,这些分区称为拥有RSet分区(an RSet’s owning region)。
    注意⚠️:每个区域Region都有一个记忆集RSet,列出了从外部指向该区域的引用。RSet中的信息是实时维护的,也就是每次产生外部引用都会立刻记录到RSet中,而不需要等待GC时才产生。
    在这里插入图片描述

  • 收集集合 (CSet)

CSet收集示意图
收集集合(CSet)代表每次GC暂停时回收的一系列目标分区。在任意一次收集暂停中,CSet所有分区都会被释放,内部存活的对象都会被转移到分配的空闲分区中。因此无论是年轻代收集,还是混合收集,工作的机制都是一致的。年轻代收集CSet只容纳年轻代分区,而混合收集会通过启发式算法,在老年代候选回收分区中,筛选出回收收益最高的分区添加到CSet中。候选老年代分区的CSet准入条件,可以通过活跃度阈值-XX:G1MixedGCLiveThresholdPercent(默认85%)进行设置,从而拦截那些回收开销巨大的对象;同时,每次混合收集可以包含候选老年代分区,可根据CSet对堆的总大小占比-XX:G1OldCSetRegionThresholdPercent(默认10%)设置数量上限。由上述可知,G1的收集都是根据CSet进行操作的,年轻代收集与混合收集没有明显的不同,最大的区别在于两种收集的触发条件

五、G1的标记过程

当堆的整体占用足够大时,并发标记开始。默认情况下,它是45%,通过参数InitiatingHeapOccupancyPercent控制。
并发标记(Concurrent Marking)阶段主要是为Mixed GC做准备

G1的标记分为以下几个阶段:
在这里插入图片描述

1、初始标记 Initial marking phase
此阶段标志着从GC根直接到达的所有对象,该阶段依赖于年轻代垃圾收集(young gc),该阶段是STW的。由于GC Roots数量不多,通常该阶段耗时非常短。事实上,当达到IHOP阈值时,G1并不会立即发起并发标记周期,而是等待下一次年轻代收集,利用年轻代收集的STW时间段,完成初始标记,这种方式称为借道(Piggybacking)

1.631: [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0062656 secs]

2、根区域扫描阶段 Root region scanning phase
扫描在初始标记阶段被标识的幸存区域,标记那些被老年代引用的存活对象。此阶段与应用程序(而非 STW)并发运行,必须在下一个 STW 年轻垃圾回收开始之前完成。

在初始标记暂停结束后,年轻代收集也完成了对象复制到Survivor的工作,应用线程开始活跃起来。此时为了保证标记算法的正确性,所有新复制到Survivor分区的对象,都需要被扫描并标记成根,这个过程称为根分区扫描(Root Region Scanning),同时扫描的Suvivor分区也被称为根分区(Root Region)。根分区扫描必须在下一次年轻代垃圾收集启动前完成(并发标记的过程中,可能会被若干次年轻代垃圾收集打断),因为每次GC会产生新的存活对象集合。

1.362: [GC concurrent-root-region-scan-start]
1.364: [GC concurrent-root-region-scan-end, 0.0028513 secs]

3、并发标记阶段 Concurrent marking phase
G1 GC 在整个堆中查找可访问的(活动的)对象。这个阶段与应用程序同时发生,并且可以被 STW 年轻的垃圾回收中断。

1.364: [GC concurrent-mark-start]
1.645: [GC concurrent-mark-end, 0.2803470 secs]

4、重标记阶段 Remark phase
重新标记(Remark)是最后一个标记阶段。在该阶段中,G1需要一个暂停的时间STW,去处理剩下的SATB日志缓冲区和所有更新,找出所有未被访问的存活对象,同时安全完成存活数据计算。这个阶段也是并行执行的,通过参数-XX:ParallelGCThread可设置GC暂停时可用的GC线程数。同时,引用处理也是重新标记阶段的一部分,所有重度使用引用对象(弱引用、软引用、虚引用、最终引用)的应用都会在引用处理上产生开销,最后还会执行一些类卸载操作。

1.645: [GC remark 1.645: [Finalize Marking, 0.0009461 secs] 1.646: [GC ref-proc, 0.0000417 secs] 1.646: [Unloading, 0.0011301 secs], 0.0074056 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]

5、清除阶段 Cleanup phase
紧挨着重新标记阶段的清除(Clean)阶段,该阶段是部分并发的。
清除阶段主要执行以下操作:

  • RSet梳理,启发式算法会根据活跃度和RSet尺寸对分区定义不同等级,同时RSet数理也有助于发现无用的引用。参数-XX:+PrintAdaptiveSizePolicy可以开启打印启发式算法决策细节;
  • 整理堆分区,为混合收集周期识别回收收益高(基于释放空间和暂停目标)的老年代分区集合;
  • 识别所有空闲分区,即发现无存活对象的分区。这类分区可在清除阶段直接回收,无需等待下次收集周期。由于这样区域没有存活对象,所以采用并发清空回收。
1.652: [GC cleanup 1213M->1213M(1885M), 0.0030492 secs]
[Times: user=0.01 sys=0.00, real=0.00 secs]

如果需要回收一部分没有存活对象的区域,则日志如下:

1.872: [GC cleanup 1357M->173M(1996M), 0.0015664 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]
1.874: [GC concurrent-cleanup-start]
1.876: [GC concurrent-cleanup-end, 0.0014846 secs]

在这里插入图片描述
注意:

  1. 在标记过程的最后一个阶段:清除阶段 Cleanup phase,会直接对没有存活对象的分区进行回收,无需等待下次收集周期。
  2. 在初始标记阶段、重标记阶段、清理阶段会出现STW,根区域扫描阶段和并发标记阶段都可以和应用程序并发执行,不会出现STW。

作为对比,CMS的老年代回收采用的是标记-清除算法,其标记过程如下:
在这里插入图片描述

  • 初始标记(CMS Initial Mark) —— 标记GC root能直接关联的对象(短暂STW)
  • 并发标记(CMS Concurrent Mark)—— GCRootsTracing,从并发标记中的root遍历,对不可达的对象进行标记
  • 重新标记(CMS Remark)—— 修正并发标记期间因为用户操作导致标记发生表更的对象,采用的incremental update算法,会出现比较多的STW
  • 并发清除(CMS Concurrent Sweep)—— 由于是直接清理,不涉及对象的复制转移,所以阶段可以并发执行。

小结:
CMS在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿,只有初始标记和重新标记会出现STW

这样看好像比G1老年代的标记-复制算法暂停时间更少(因为G1的清理阶段也会出现STW暂停)。其实不然:
1、G1的初始标记阶段是在Young GC的STW过程中同步完成的。
3、重新标记阶段,当堆内存太大时,CMS重新标记的STW时间会逐渐不可控,而G1的重新标记利用RSet采用的SATB算法STW时间非常短暂。
2、CMS会对老年代全区域进行回收,而G1采用预测性算法对老年代Region的回收性价比排序,每次都是在保证暂停时间可控的情况下回收性价比最高的内存Region,所以单次回收的STW时间更加可控。

六、G1的垃圾回收

G1提供了两种GC模式,Young GC和Mixed GC,两种都是完全Stop The World的。
在这里插入图片描述

1、G1过程梳理

  1. 应用程序启动时,会首先向服务器申请分配JVM内存,并将申请到的内存划分为许多大小相同的区域Region,这时的区域都是空闲状态 (Free regions)。
  2. 当应用程序开始运行后,会持续产生新的对象,G1内存管理器会分配空闲(Free regions)作为年轻代的伊甸区(eden regions)存放这些新产生的对象。如果新产生的对象大于Region的一半,则直接放入老年区的大对象区域(Humongous Region)。
  3. G1 GC为了匹配软实时(soft real-time)的目标会动态调整年轻代的大小,当年轻代被填满后,就会触发Young GC,Young GC会对整个年轻代和大对象区域(Humongous Region)进行回收。Young GC结束后依然存活的对象,会被疏散evacuation到n(n>=1)个新的Survivor分区,或者是老年代。
  4. 当java heap占用达到 InitiatingHeapOccupancyPercent 定义的阈值之后,在下一个Young GC开始的时候,同时开始进行并发标记(Concurrent Marking)。
  5. 并发标记(Concurrent Marking)和Young GC穿插执行,在Concurrent Marking的过程中可能会出现多次Young-only GC。
  6. 在并发标记(Concurrent Marking)的清理阶段,会直接回收无存活对象的分区。
  7. 当并发标记(Concurrent Marking)结束后,会根据-XX:G1HeapWastePercent=5设置的阈值判断是否需要执行Mixed GC。
  8. 在Mixed GC阶段,会对所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region进行回收。
  9. 如果Mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用Serial old GC(full GC)来收集整个java堆空间。注意G1本身并不提供full GC的。

G1中的垃圾回收主要分为2类:Young GC和Mixed GC
在这里插入图片描述
说明:
1、图中的圆圈表示G1回收过程中的暂停:蓝色圆圈表示Young-only GC导致的暂停,红色圆圈表示Mixed GC导致的暂停,黄色圆圈表示有并发标记导致的暂停。
2、当java heap占用达到 InitiatingHeapOccupancyPercent 定义的阈值之后,下一个Young-only GC收集将也会进行并发标记的初始标记,如图中大蓝色圆圈。
3、Young-only GC和Concurrent Marking阶段可以穿插执行,在Concurrent Marking的过程中可能会出现多次Young-only GC,而Mixed GC只能在Concurrent Marking阶段完成后才能执行。
4、当完成并发标记阶段后,不一定会立刻进行Mixed GC,也可能会进行几次Young-only GC后才会进行Mixed GC。(可能并没有达到G1HeapWastePercent设置的阈值)
5、蓝色圆圈数量多于红色圆圈数量,表示一般情况下,Young-only GC发生的次数往往要大于Mixed GC的次数,这也是G1努力使垃圾回收更加高效。

2、Young GC

年轻代垃圾回收阶段,该阶段也被称为Young-only或Fully Young阶段,会对整个年轻代的区域Region进行回收。

Eden区耗尽的时候就会触发新生代收集,新生代垃圾收集会对整个新生代(E + S)进行回收。

  • 新生代垃圾收集期间,整个应用STW
  • 新生代垃圾收集是由多线程并发执行的
  • 通过控制年轻代的region个数,即年轻代内存大小,来控制young GC的时间开销。
  • 新生代收集结束后依然存活的对象,会被疏散evacuation到n(n>=1)个新的Survivor分区,或者是老年代。
  • 该阶段会进行大对象区域的回收

Young GC日志示例:
该次Young GC暂停过程中,同时进行了大对象的分配,并完成了并行标记的初始化标记。

2023-02-10T09:43:54.663+0800: 10690912.762: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0274320 secs]
   [Parallel Time: 21.8 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 10690912762.4, Avg: 10690912762.4, Max: 10690912762.6, Diff: 0.2]
      [Ext Root Scanning (ms): Min: 3.2, Avg: 3.5, Max: 3.7, Diff: 0.5, Sum: 14.1]
      [Update RS (ms): Min: 16.6, Avg: 16.7, Max: 17.1, Diff: 0.5, Sum: 66.9]
         [Processed Buffers: Min: 211, Avg: 216.8, Max: 225, Diff: 14, Sum: 867]
      [Scan RS (ms): Min: 0.1, Avg: 0.3, Max: 0.5, Diff: 0.4, Sum: 1.4]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.5, Avg: 0.7, Max: 0.9, Diff: 0.4, Sum: 2.9]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.1]
         [Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 4]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
      [GC Worker Total (ms): Min: 21.3, Avg: 21.4, Max: 21.4, Diff: 0.1, Sum: 85.6]
      [GC Worker End (ms): Min: 10690912783.8, Avg: 10690912783.8, Max: 10690912783.8, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [String Dedup Fixup: 2.4 ms, GC Workers: 4]
      [Queue Fixup (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Table Fixup (ms): Min: 2.1, Avg: 2.2, Max: 2.3, Diff: 0.3, Sum: 8.9]
   [Clear CT: 0.3 ms]
   [Other: 2.9 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.7 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.1 ms]
      [Humongous Reclaim: 0.6 ms]
      [Free CSet: 0.9 ms]
   [Eden: 2170.0M(2454.0M)->0.0B(2452.0M) Survivors: 2048.0K->4096.0K Heap: 3693.3M(4096.0M)->946.1M(4096.0M)]
 [Times: user=0.10 sys=0.00, real=0.03 secs] 

3、Mixed GC

混合垃圾回收阶段,该阶段也被称为Space-reclamation阶段,会选定所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region进行回收。

  • 整个阶段都是STW
  • 所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region进行回收
  • Mixed GC不是Full GC,它只能回收部分老年代的Region,如果Mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用Serial old GC(full GC)来收集整个GC heap。G1本身并不提供full GC的。
  • 该阶段会进行大对象区域的回收
2023-02-10T09:07:24.661+0800: 10688722.759: [GC pause (G1 Evacuation Pause) (mixed), 0.0105385 secs]
   [Parallel Time: 7.1 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 10688722759.3, Avg: 10688722759.3, Max: 10688722759.4, Diff: 0.0]
      [Ext Root Scanning (ms): Min: 1.6, Avg: 2.0, Max: 2.6, Diff: 1.1, Sum: 7.9]
      [Update RS (ms): Min: 3.9, Avg: 4.4, Max: 4.6, Diff: 0.7, Sum: 17.4]
         [Processed Buffers: Min: 48, Avg: 52.8, Max: 56, Diff: 8, Sum: 211]
      [Scan RS (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.4]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.5, Avg: 0.6, Max: 0.7, Diff: 0.3, Sum: 2.3]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 4]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Total (ms): Min: 7.0, Avg: 7.0, Max: 7.1, Diff: 0.0, Sum: 28.2]
      [GC Worker End (ms): Min: 10688722766.4, Avg: 10688722766.4, Max: 10688722766.4, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [String Dedup Fixup: 2.3 ms, GC Workers: 4]
      [Queue Fixup (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Table Fixup (ms): Min: 2.2, Avg: 2.2, Max: 2.2, Diff: 0.0, Sum: 8.8]
   [Clear CT: 0.1 ms]
   [Other: 1.0 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.3 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.1 ms]
      [Humongous Reclaim: 0.1 ms]
      [Free CSet: 0.2 ms]
   [Eden: 204.0M(204.0M)->0.0B(2454.0M) Survivors: 0.0B->2048.0K Heap: 1255.0M(4096.0M)->944.2M(4096.0M)]
 [Times: user=0.04 sys=0.00, real=0.01 secs] 

4、Full GC

作为一种兜底的备份策略,如果mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(full GC)来收集整个GC heap。

  • 整个过程都是STW
  • G1并不提供full GC的,这个Serial old GC提供的。Full gc是单线程的(在Java 8中)并且非常慢,因此应避免在G1 GC的时候出现这个Full GC

不能觉得用了G1 GC收集器之后,Java heap里面的GC不是Young GC 就Mixed GC,还是存在Full GC。
G1 GC一旦发生了Full GC,就说明当前程序的运行可能出现问题的,需要考虑为什么会Full GC了?

G1是如何满足目标暂停时间的?
前提:G1的JVM内存模型:在物理上分区region、逻辑上分代

  1. 在年轻代收集(Young GC)期间,G1 GC 调整年轻代(伊甸园和幸存者)的大小以来匹配软实时(soft real-time)的目标。
  2. 在混合收集(Mixed GC)期间,G1 GC 根据混合垃圾收集的目标数量、堆中每个区域中活动对象的百分比以及总体可接受的堆浪费百分比来调整收集的旧区域的数量,以满足目标暂停时间目标。

七、参数介绍

参数说明
-XX:G1HeapRegionSize=n设置 G1区域Region的大小。范围从1 MB 到32MB之间,目标是根据最小的 Java 堆大小划分出大约2048个区域。
-XX:MaxGCPauseMillis=200设置最长暂停时间目标值,默认是200毫秒
-XX:G1NewSizePercent=5设置年轻代最小值占总堆的百分比,默认值是5%
-XX:G1MaxNewSizePercent=60设置年轻代最大值占总堆的百分比,默认值是java堆的60%
-XX:ParallelGCThreads=n设置STW并行工作的GC线程数,一般推荐设置该值为逻辑处理器的数量,最大是8;如果逻辑处理器大于8,则取逻辑处理器数量的5/8;这适用于大多数情况,除非是较大的SPARC系统,其中的n值可以是逻辑处理器的5/16
-XX:ConcGCThreads=n并发标记阶段,并发执行的线程数,一般n值为并行垃圾回收线程数(ParallelGCThreads)的1/4左右
-XX:InitiatingHeapOccupancyPercent=45设置触发全局并发标记周期的Java堆内存占用率阈值,默认占用率阈值是整个Java堆的45%
-XX:G1MixedGCLiveThresholdPercent=85老年代Region中存活对象的占比,只有当占比小于此参数的Old Region,才会被选入CSet。这个值越大,说明允许回收的Region中的存活对象越多,可回收的空间就越少,gc效果就越不明显
-XX:G1HeapWastePercent=5设置G1中愿意浪费的堆的百分比,如果可回收region的占比小于该值,G1不会启动Mixed GC,默认值10%,主要用来控制Mixed GC的触发时机。在global concurrent marking结束之后,我们可以知道老年代regions中有多少空间要被回收,在每次YGC之后和再次Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会触发Mixed GC。
-XX:G1MixedGCCountTarget=8一次全局并发标记后,最多执行Mixed GC的次数,次数越多,单次回收的老年代的Region个数就越少,暂停也就越短
-XX:G1OldCSetRegionThresholdPercent=10一次Mixed GC过程中老年代Region内存最多能被选入CSet中的占比
-XX:G1ReservePercent=10设置作为空闲空间的预留内存百分比,用来降低目标空间溢出的风险,默认是10%,一般增加或减少百分比时,需要确保也对java堆调整相同的量。

如何解锁VM经验值标志参数?
在Java8中G1的众多参数中,包含3个经验值参数,如果需要调整经验值参数的值,需要先解锁经验值标志。

3个经验值:

-XX:G1NewSizePercent=5
-XX:G1MaxNewSizePercent=60
-XX:G1MixedGCLiveThresholdPercent=85

实例:添加UnlockExperimentalVMOptions参数,并调整参数G1NewSizePercent参数的经验值

java -XX:+UnlockExperimentalVMOptions  -XX:G1NewSizePercent=10 -XX:G1MaxNewSizePercent=75 G1test.jar

注意事项:

  • 避免通过-Xmn 或其他相关选项(如-XX: NewRate)显式设置年轻代的大小,因为年轻代的大小被固定后会导致G1的目标暂停机制失效。
  • 当设置目标暂停时间MaxGCPauseMillis时,需要评估G1 GC 的延迟和应用程序吞吐量之间的取舍,当该值设置较小时,表明您愿意承担垃圾收集开销的增加,从而会导致应用程序吞吐量的降低。
  • Mixed GC的调优:
    1)-XX:InitiatingHeapOccupancyPercent: 控制并发标记开始的内存占比阈值
    2)-XX:G1HeapWastePercent: 设置G1中愿意浪费的堆的百分比,如果可回收region的占比小于该值,G1不会启动Mixed GC,默认值10%,主要用来控制Mixed GC的触发时机。
    3)-XX:G1MixedGCLiveThresholdPercent:设置老年代Region进入CSet的活跃对象占比阈值,避免活跃对象占比过高的Region进入CSet
    3)-XX:G1MixedGCCountTarget and -XX:G1OldCSetRegionThresholdPercent: 主要是为了控制单次Mixed GC中Region的个数,CSet中Region的个数越多,GC过程中暂停时间越长。

八、典型问题

1、疏散失败(Evacuation Failure)

当没有更多的空闲region被提升到老一代或者复制到幸存空间时,并且由于堆已经达到最大值,堆不能扩展,从而发生Evacuation Failure,这时G1 的GC已经无能为力,只能使用通过Serial old GC进行Full GC来收集整个java堆空间,这个过程就是转移失败(Evacuation Failure)。

Young GC 疏散暂停(Evacuation Pause)过程出现内存耗尽的对应日志:
在这里插入图片描述
注意:G1 Evacuation Pause指的是G1垃圾回收过程中存活对象的复制-转移阶段,被称为‘疏散暂停’阶段。

解决方案:

  • 如果有大量“空间耗尽(to-space exhausted)”或“空间溢出(to-space overflow)”GC事件,则增加-XX:G1ReservePercent以增加“to-space”的预留内存量,默认值是Java堆的10%。注意:G1 GC将此值限制在50%以内。
  • 通过减少 -XX: InitiatingHeapOccupancyPercent 的值来更早地启动并发标记周期,来及时回收不包含活跃对象的区域,同时促使Mixed GC更快发生。
  • 增加选项-XX:ConcGCThreads的值以增加并行标记线程的数量,减少并行标记阶段的耗时。

2、大对象分配(Humongous Allocation)

Young GC过程中大对象分配时出现内存耗尽的对应日志:
在这里插入图片描述
原因分析:
出现大对象分配导致的内存耗尽问题,一般是老年代剩余的Region中已经不能够找到一组连续的区域分配给新的巨型对象。

解决方案:

  • 通过-XX: G1HeapRegionSize 选项来增加内存区域Region的大小,提升Region对象的判断标准,以减少巨大对象的数量。
  • 增加堆java的大小使得有更多的空间来存放巨型对象
  • 通过-XX:G1MaxNewSizePercent降低年轻代Region的占比,给老年代预留更多的空间,从而给巨型对象提供给多的内存空间。

一般在疏散阶段(Evacuation Pause)和大对象分配(Humongous Allocation)会比较容易出现“空间耗尽(to-space exhausted)”或“空间溢出(to-space overflow)”的GC事件,导致出现转移失败(Evacuation Failure) ,进而引发Full GC 从而导致GC的暂停时间超过G1的设置的目标暂停时间。
所以我们要尽量避免出现转移失败(Evacuation Failure)。
在这里插入图片描述

3、Young GC花费时间太长

通常Young GC的耗时与年轻代的大小成正比,具体地说,是需要复制的集合集中的活跃对象的数量。

如果Young GC中CSet的疏散阶段(Evacuate Collection Set phase)需要很长时间,尤其是其中的对象复制-转移,可以通过降低-XX:G1NewSizePercent的值,降低年轻代的最小尺寸,从而降低停顿时间。

还可以使用-XX:G1MaxNewSizePercent降低年轻代的最大占比,从而减少Young GC暂停期间需要处理的对象数量。

4、Mixed GC耗时太长

  • 通过降低-XX:InitiatingHeapOccupancyPercent的值,来调低并发标记阶段开始的阈值,让并发标记阶段更早触发,只有并发标记完成才能开始执行Mixed GC。
  • 通过调节-XX:G1MixedGCCountTarget-XX:G1OldCSetRegionThresholdPercent参数,降低单次回收的Region数量,减少暂停时间。
  • 通过调节-XX:G1MixedGCLiveThresholdPercent的值,避免活跃对象占比过高的Region进入CSet。因为活的对象越多,region中可回收的空间就越少,暂停时间就越长,gc效果就越不明显。
  • 通过调节-XX:G1HeapWastePercent的值,设置愿意浪费的堆的百分比。只有垃圾占比大于此参数,才会发生Mixed GC,该值越小,会越早触发Mixed GC。

总结

本来主要对G1垃圾回收器的相关特性和实现机制做了详细介绍。
1、首先G1的内存模型进行了说明:物理上分区,逻辑上分代,年轻代和老年代的区域不是连续的内存空间,而是由分散的大小相同的内存块Region组成,G1 GC会通过动态条件年轻代区域Region的数量以来匹配软实时(soft real-time)的目标。
2、详解介绍了G1 GC中的完整的垃圾回收流程以及Young GC、并发标记和Mixed GC的实现细节。
3、介绍了G1 GC的优缺点以及选用场景。
4、对G1的核心参数做了相关介绍,并针对G1的重点问题提供了参数优化建议。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/343727.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】动静态库以及动静态链接

环境:centos7.6,腾讯云服务器Linux文章都放在了专栏:【Linux】欢迎支持订阅🌹链接扩展我们在使用Linux的时候,不禁会有这么一个疑问:为什么我们能够在Linux下进行c/c代码的编写以及编译呢?这是因…

_Linux(网络基础)

文章目录1. 相关基础概念2. 认识 "协议"3. 网络协议初识协议分层OSI七层模型TCP/IP五层(或四层)模型4. 网络传输基本流程网络传输流程图数据包封装和分用小结5. 网络中的地址管理认识IP地址认识MAC地址认识端口号1. 相关基础概念 独立模式: 计算机之间相互独立网络互…

MySQL数据库13——插入数据(INSERT)

下面的语句用于向student表插入数据。 插入语句: INSERT INTO student(ID,name,sex,birthday,origin,contact1,contact2,institute) VALUES (0013,塔赛努,男,1997/9/15,内蒙古自治区,NULL,NULL,计算机学院);INSERT INTO student VALUES (0014,呼和嘎拉,男,1995-02…

字节青训营——秒杀系统设计学习笔记(一)

如何做系统设计 1. 场景分析(Scenario) 什么系统,需要哪些功能,多大的并发量 2. 存储设计(Storage) 数据如何组织,Sq|存储, NoSq|存储 3. 服务设计(Service) 业务功能实现和逻辑整合 4. 可扩展性(Scale) 解决设计缺陷&…

Go的web开发Gin框架1(八)——Gin

一、重点内容: 知识要点有哪些? 1、了解Gin框架 2、导入使用Gin框架 3、尝试配合GORM开发 4、整合html,css,js 二、详细知识点介绍: 1、Gin框架介绍 ​ Gin是一个golang的微框架,封装比较优雅&…

MyBatis无法通过getGenerateKeys获得自增主键的问题

我遇到这个问题的法伤原因比较蠢,查阅了网上相关经验都没有能够解决。看看这个经验能否帮助到你。问题描述:设置了属性的自增后想通过getGenerateKeys获得MySQL对应表单中自增主键id,检查了类、映射器、xml都没有发现问题,但是进行…

如何使用 Python 编程进行多线程

多线程:理论上能在同一个时间段执行多个程序片段,每个程序片段就看作是一个线程。为什么要说理论上,因为实际在操作系统中真正的在同一时间段基本是不存在的,但是在软件编程中我们可以理解为它是在同一时间段执行的。 同步&#…

c/c++开发,无可避免的模板编程实践(篇一)

一、c模板 c开发中,在声明变量、函数、类时,c都会要求使用指定的类型。在实际项目过程中,会发现很多代码除了类型不同之外,其他代码看起来都是相同的,为了实现这些相同功能,我们可能会进行如下设计&#xf…

SpringBoot自定义JsonSerializer和JsonDeserializer,兼容LocalDateTime和LocalDate

1.前言 JDK1.8中添加新的时间日期API,LocalDate、LocalDateTime、LocalTime,但是我们在开发中使用时间戳作为参数值来传递是比较常用的,然而在SpringBoot中并没有为我们提供合适的JsonSerializer和JsonDeserializer。 我们先看看使用默认的Js…

Linux根文件系统移植

目录 一、根文件系统 1.1根文件系统 1.2根文件系统内容 二、根文件系统移植 2.1BusyBox 2.2BusyBox的获取 2.3BusyBox的使用 2.4make menuconfig 2.5编译和安装 2.6修改根文件系统 一、根文件系统 1.1根文件系统 根文件系统是内核启动后挂载的第一个文件系统系统引…

Homekit智能家居创意DIY一智能灯

一、什么是智能灯 传统的灯泡是通过手动打开和关闭开关来工作。有时,它们可以通过声控、触控、红外等方式进行控制,或者带有调光开关,让用户调暗或调亮灯光。 智能灯泡内置有芯片和通信模块,可与手机、家庭智能助手、或其他智能…

链表题目总结 -- 递归

目录一. 递归反转整个链表1. 思路简述2. 代码3. 总结二. 反转链表前 N 个节点1. 思路简述2. 代码3. 总结三、反转链表的一部分1. 思路简述2. 代码3.总结四、反转链表后N个节点1. 思路简述2. 代码3.总结一. 递归反转整个链表 题目链接:https://leetcode.cn/problems…

部署智能合约到公链

🍁博主简介: 🏅云计算领域优质创作者 🏅2022年CSDN新星计划python赛道第一名 🏅2022年CSDN原力计划优质作者 🏅阿里云ACE认证高级工程师 🏅阿里云开发者社区专…

【面试】生命周期详情解释及案例

目录 1.什么是生命周期 2.生命周期函数 3.vue2中生命周期的函数 4.生命周期的阶段 5.生命周期图示 第一个阶段:组件创建阶段 第二个阶段:编译HTML模板并渲染到浏览器中 第三阶段:组件更新阶段 第四阶段:组件销毁阶段 1.什…

【数据结构】基础:图的基本概念与实现(附C++源代码)

【数据结构】基础:图的基本概念与实现(附C源代码) 摘要:将会在数据结构专题中开展关于图论的内容介绍,其中包括四部分,分别为图的概念与实现、图的遍历、图的最小生成树以及图的最短路径问题。本文将介绍图…

遗传算法(Genetic Algorithm,GA)实现数据排序,python

遗传算法(Genetic Algorithm,GA)实现数据排序,python 遗传算法是一种比较广泛、通用的算法体系,为了说明遗传算法的原理和实现,现在用GA解决一个计算机科学最基本、最古老的问题:排序问题。 需要特别说明的是,遗传算…

【GPLT 三阶题目集】L3-016 二叉搜索树的结构

二叉搜索树或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;它的左、右子树也分…

代码随想录LeetCode | 单调栈问题

前沿:撰写博客的目的是为了再刷时回顾和进一步完善,其次才是以教为学,所以如果有些博客写的较简陋,是为了保持进度不得已而为之,还请大家多多见谅。 预:看到题目后的思路和实现的代码。 见:参考…

两种特征提取方法与深度学习方法对比的小型金属物体分类分析研究

本文讨论了用于对包括螺丝、螺母、钥匙和硬币在内的小型金属物体进行分类的两种特征提取方法的效率:定向梯度直方图 (HOG) 和局部二进制模式 (LBP)。首先提取标记图像的所需特征并以特征矩阵的形式保存。使用三种不同的分类方法(非参数 K 最近邻算法、支…

云计算|OpenStack|社区版OpenStack(实务操作---cloud-init的使用)

前言: 接上一篇文章:https://zskjohn.blog.csdn.net/article/details/128931042 我们可以从官方获取到现成的镜像,例如,从Ubuntu 18.04 LTS (Bionic Beaver) Daily Build [20230210]官方下载的bionic-server-cloudimg-amd64.img…