G1垃圾回收器详解
文章目录
- 前言
- 一、思考问题
- 二、官方文档
- 三、基本介绍
- 四、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垃圾回收器,那么这篇文章你可以跳过了。如果还存在疑问,希望本文能给你解惑。
- G1有哪些特点?
- G1是分区还是分代?
- G1的一个内存单元Region中可以同时包含年轻代和老年代吗?
- G1相比CMS有哪些优点?相比ZGC有什么缺点?
- G1配置过程中,有哪些重要参数和注意事项?
- G1的使用过程中,遇到过哪些问题,怎么解决的?
- G1怎么实现目标暂停时间的?
- G1垃圾回收的过程中哪些阶段会出现STW?
- G1中哪些情况对象会被转移到老年代Old Genetion?
- G1中触发GC的时机?
- G1有哪些缺点?
- G1的适用场景?
- G1能否支持上T的大内存?
- 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带来的优化,并且确定一片连续的内存空间需要扫描整堆,因此确定巨型对象开始位置的成本非常高,如果可以,应用程序应避免生成巨型对象。
对于巨型对象,有以下几个点需要注意:
-
没有被引用的巨型对象会在标记清理阶段或者Full GC时被释放掉。
-
Young GC和Mixed GC阶段都会对巨型对象进行回收
-
巨型对象永远不会移动,即使在Full GC中
-
每一个region中都只有一个巨型对象,该region剩余的部分得不到利用,会导致堆碎片化。
-
如果看到由于大对象分配导致频繁的并发回收,需要把大对象变为普通的对象,建议增大Region size(或者切换到ZGC)。但是增大Region size有一个负面影响就是:减少了可用region的数量。因此,对于这种情况,你需要进行相应的测试,以查看是否实际提高了应用程序的吞吐量或延迟。
-
在分配巨型对象之前会先检查是否超过 initiating heap occupancy percent和the marking threshold, 如果超过的话,就启动global concurrent marking,为的是提早回收,防止 evacuation failures 和 Full GC。
-
记忆集合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)代表每次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]

注意:
- 在标记过程的最后一个阶段:清除阶段 Cleanup phase,会直接对没有存活对象的分区进行回收,无需等待下次收集周期。
- 在初始标记阶段、重标记阶段、清理阶段会出现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过程梳理
- 应用程序启动时,会首先向服务器申请分配JVM内存,并将申请到的内存划分为许多大小相同的区域Region,这时的区域都是空闲状态 (Free regions)。
- 当应用程序开始运行后,会持续产生新的对象,G1内存管理器会分配空闲(Free regions)作为年轻代的伊甸区(eden regions)存放这些新产生的对象。如果新产生的对象大于Region的一半,则直接放入老年区的大对象区域(Humongous Region)。
- G1 GC为了匹配软实时(soft real-time)的目标会动态调整年轻代的大小,当年轻代被填满后,就会触发Young GC,Young GC会对整个年轻代和大对象区域(Humongous Region)进行回收。Young GC结束后依然存活的对象,会被疏散evacuation到n(n>=1)个新的Survivor分区,或者是老年代。
- 当java heap占用达到 InitiatingHeapOccupancyPercent 定义的阈值之后,在下一个Young GC开始的时候,同时开始进行并发标记(Concurrent Marking)。
- 并发标记(Concurrent Marking)和Young GC穿插执行,在Concurrent Marking的过程中可能会出现多次Young-only GC。
- 在并发标记(Concurrent Marking)的清理阶段,会直接回收无存活对象的分区。
- 当并发标记(Concurrent Marking)结束后,会根据-XX:G1HeapWastePercent=5设置的阈值判断是否需要执行Mixed GC。
- 在Mixed GC阶段,会对所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region进行回收。
- 如果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、逻辑上分代
- 在年轻代收集(Young GC)期间,G1 GC 调整年轻代(伊甸园和幸存者)的大小以来匹配软实时(soft real-time)的目标。
- 在混合收集(Mixed GC)期间,G1 GC 根据混合垃圾收集的目标数量、堆中每个区域中活动对象的百分比以及总体可接受的堆浪费百分比来调整收集的旧区域的数量,以满足目标暂停时间目标。
七、参数介绍
| 参数 | 说明 |
|---|---|
| -XX:+UseG1GC | 使用 G1 收集器 |
| -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:G1MixedGCCountTargetand-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的重点问题提供了参数优化建议。
相关文章:
G1垃圾回收器详解
文章目录前言一、思考问题二、官方文档三、基本介绍四、G1的内存模型五、G1的标记过程六、G1的垃圾回收1、G1过程梳理2、Young GC3、Mixed GC4、Full GC七、参数介绍八、典型问题1、疏散失败(Evacuation Failure)2、大对象分配(Humongous All…...
tws耳机哪个牌子音质好?tws耳机音质排行榜
随着蓝牙耳机市场的不断发展,使用蓝牙耳机的人也逐渐增多,近年来更是超越有线耳机成为最火爆的数码产品之一。那么,tws耳机哪个牌子音质好?下面,我来给大家推荐几款音质好的tws耳机,可以当个参考。 一、南…...
TIA博途中DB数据块清零的具体方法示例
TIA博途中DB数据块清零的具体方法示例 TIA中数据块如何实现清零? 在TIA指令集内有多个移动指令可对DB块内数据进行清零处理。对于S7-1500 CPU或ET200SP CPU来说,可使用BLKMOV、FILL以及SCL的POKE_BLK指令。但是这些指令对DB块清零时,要求DB块必需为非优化DB。 对于优化的DB…...
iptables防火墙屏蔽指定ip的端口
因为需要测试客户端程序与hadoop服务器之间正常通信需要开通的端口, 所以在hadoop各服务器上使用iptables防火墙屏蔽了测试客户端程序的ip和所有端口。然后,根据报错信息提示的端口号来逐步放开直到能正常通信下载文件。 在服务器端屏蔽指定ip访问所有端口 #查看…...
JavaScript Math(算数) 对象
JavaScript Math(算数) 对象 Math 是一个内置对象,它拥有一些数学常数属性和数学函数方法。Math 不是一个函数对象。 Math 用于 Number 类型。它不支持 BigInt。 描述 与其他全局对象不同的是,Math 不是一个构造器。Math 的所…...
超详细的JAVA高级进阶基础知识04
目录 4. 面向对象高级 - 常用的API 4.1 Arrays 工具类 4.1.1 Arrays 类介绍 4.2 冒泡排序 4.3 选择排序 4.4 二分查找 4.5 正则表达式 4.5.1 String 类中与正则有关的常见方法 4.5.2 练习 4.5.3 今日学习目标 4. 面向对象高级 - 常用的API 4.1 Arrays 工具类 4.1.1…...
Python 运算符?
什么是运算符? 本章节主要说明Python的运算符。举个简单的例子 4 5 9 。 例子中,4 和 5 被称为操作数, 称为运算符。 Python语言支持以下类型的运算符: 算术运算符 比较(关系)运算符 赋值运算符 逻辑运算符 位运算符…...
linux nuxt 部署 问题汇总
安装node centos 7 请选择 node版本 v16.1.0 进行安装, npm 版本 7.11.2 亲测这个版本有效,否则会出现 Error: Cannot find module ‘node:fs/promises‘ 等问题 node安装亲参照: Linux node 安装教程_linux node安装_围城少年的博客…...
C++内存管理
文章目录1. c的内存管理例题2.c管理方式1.c的内置类型1.申请一个空间并初始化2.申请连续的空间并初始化3.总结2.c的自定义类型2.总结3.operator new与operator delete函数4.new和delete的实现原理1.内置类型2.自定义类型内存泄露问题&&delete先析构的原因编译器实现机制…...
电子招投标系统源码之 —采购数字化转型快人一步,以大数据支撑供应链管理未来
采购数字化转型快人一步,以大数据支撑供应链管理未来 招标采购为主的一站式全流程数字化采供协同。平台满足询比价、招标、竞价、拍卖、协议直采、商城采购等多种采购定价方式,采购过程全程留痕可追溯。平台支持企业通过WEB、APP、小程序等终端完成采购需…...
ie获取cookie数据,中文乱码;cookie中文乱码终极解决办法
终极解决办法 cookie存中文数据是会出现乱码的,所以在存数据前,得先“编码”,取的时候先“解码” JS方法-编码:encodeURI("你好") 结果:"%E4%BD%A0%E5%A5%BD"JS方法-解码:decodeURI(&…...
day16_关键字this和super丶就近原则和追根溯源原则
this关键字 含义:this代表当前对象 this使用位置 this在实例初始化相关的代码块和构造器中:表示正在创建的那个实例对象,即正在new谁,this就代表谁this在非静态实例方法中:表示调用该方法的对象,即谁在调…...
MySQL 共享锁 (lock in share mode),排他锁 (for update)
共享锁 (lock in share mode) 简介 允许不同事务之间加共享锁读取,但不允许其它事务修改或者加入排他锁 如果有修改必须等待一个事务提交完成,才可以执行,容易出现死锁 共享锁事务之间的读取 session1: start transaction; select * from…...
类与对象(下)
文章目录类与对象(下)1. 再谈构造函数1.1 构造函数体赋值1.2 初始化列表特点推荐坑1.3 explicit关键字2. static成员2.1 概念面试题解法1解法2解法3(重要)2.2 特性问题3. 友元3.1 友元函数说明3.2 友元类4. 内部类4.14.25. 匿名对象6.拷贝对象时的一些编译器优化总结类与对象(下…...
feign技巧 - form方式传值
feign技巧 - form方式传值。 0. 文章目录1. 前言2. 调用样例3. 原理解析3.1 feign端序列化参数3.2 SpringMVC服务端解析参数3.3 补充 - 继承关系不会被传递的原因3.4 补充 - 不能使用GET。4. 总结1. 前言 直接正题。 如何使用feign进行fom表单方式的请求调用,以及其…...
MATLAB | 情人节来绘制更立体的玫瑰花吧
又是一年情人节,今年带来一款更有立体感的玫瑰: 曲面的函数表达式来自: http://www.bugman123.com/Math/index.html 这个网站,上面还有很多其他帅气的玩意。 基础绘制 xlinspace(0,1,300); thetalinspace(-2*pi,15*pi,300); [x,theta]meshg…...
【Python表白代码】 2.14“Valentine‘s Day”“没别的意思 就是借着特殊日子说声喜欢你”你在哪儿?我去见你~(各种玫瑰源码合集)
导语 Valentines Day Every man is a poet when he is in love 所有文章完整的素材源码都在👇👇 粉丝白嫖源码福利,请移步至CSDN社区或文末公众hao即可免费。 哈喽!我是你们的木木子吖~ 情人节又到了,礼物备好了没&am…...
压力应变电桥信号隔离放大变送器差分输入0-±10mV/0-±20mV转0-20mA/0-10v
概述:DIN11 IPO 压力应变桥信号处理系列隔离放大器是一种将差分输入信号隔离放大、转换成按比例输出的直流信号导轨安装变送模块。产品广泛应用在电力、远程监控、仪器仪表、医疗设备、工业自控等行业。此系列模块内部嵌入了一个高效微功率的电源,向输入…...
Linux系统之部署个人导航页
Linux系统之部署个人导航页 一、本次导航页工具介绍二、检查本地系统环境1.检查系统版本2.检查系统内核版本三、下载导航页软件包1.创建下载目录2.下载导航页软件包四、部署前环境准备工作1.安装python32.安装pipenv3.创建虚拟环境①创建环境②修改base.py文件③修改settings.p…...
四、Windows 平台安装 MongoDB
MongoDB 提供 64 位系统的预编译二进制包 我们可以从 MongoDB 官网下载安装 MongoDB 预编译二进制包下载地址:Try MongoDB Atlas Products | MongoDB 在 MongoDB 2.2 版本后已经不再支持 Windows XP 系统 最新版本也已经没有了 32 位系统的安装文件 MongoDB for W…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
论文阅读:Matting by Generation
今天介绍一篇关于 matting 抠图的文章,抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法,已经有很多的工作和这个任务相关。这两年 diffusion 模型很火,大家又开始用 diffusion 模型做各种 CV 任务了&am…...
