G1 GC详解及设置

一、概述

G1 GC,全称Garbage-First Garbage Collector,在JDK1.7中引入了G1 GC,从JAVA 9开始,G1 GC是默认的GC算法。通过-XX:+UseG1GC参数来启用。G1收集器是工作在堆内不同分区上的收集器,分区既可以是年轻代也可以是老年代,同一个代的分区不需要连续。并且每个代分区的数量是可以动态调整的。为老年代设置分区的目的是老年代里有的分区垃圾多,有的分区垃圾少,这样在回收的时候可以专注于收集垃圾多的分区,这也是G1名称的由来。不过这个算法并不适合新生代垃圾收集,因为新生代的垃圾收集算法是复制算法,但是新生代也使用了分区机制主要是因为便于代大小的调整。

G1 功能包括:

  • 堆大小可达数十 GB 或更大,超过 50% 的 Java 堆被实时数据占用。
  • 对象分配和提升的速率可能会随时间而显著变化。
  • 堆中存在大量碎片。
  • 可预测的暂停时间目标目标不超过几百毫秒,避免长时间的垃圾回收暂停。

G1 GC是设计用来取代CMS的,同CMS相比G1有以下优势:

1、可预测的停顿模型

2、避免了CMS的垃圾碎片

3、超大堆的表现更出色

G1里面的Region的概念不同于传统的垃圾回收算法中的分区的概念。G1默认把堆内存分为1024个分区,后续垃圾收集的单位都是以Region为单位的。Region是实现G1算法的基础,每个Region的大小相等,通过-XX:G1HeapRegionSize参数可以设置Region的大小

G1的内存划分和主要收集过程

G1收集回收器将堆进行分区,划分为一个个的区域,每次收集的时候,只收集其中几个区域,以此来控制垃圾回收产生一次停顿时间。

G1的收集过程可能有4个阶段:

  1. 新生代GC
  2. 并发标记周期
  3. 混合收集
  4. (如果需要)进行Full GC。

二、关键概念

SATB

SATB的全称是Snapchat-At-The_Beginning。SATB是维持并发GC的一种手段。G1并发的基础就是SATB。SATB可以理解成在GC开始之前对堆内存里的对象做一次快照,此时活的对象就认为是活的,从而形成一个对象图。在GC收集的时候,新生代的对象也认为是活的对象,除此之外其他不可达的对象都认为是垃圾对象。

RSet

RSet全称是Remember Set,每个Region中都有一个RSet,记录的是其他Region中的对象引用本Region对象的关系(谁引用了我的对象)。G1里面还有另外一种数据结构就Collection Set(CSet),CSet记录的是GC要收集的Region的集合,CSet里的Region可以是任意代的。在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可

停顿预测模型

G1收集器突出表现出来的一点是通过一个停顿预测模型来根据用户配置的停顿时间来选择CSet的大小,从而达到用户期待的应用程序暂停时间。通过-XX:MaxGCPauseMillis参数来设置。这一点有点类似于ParallelScavenge收集器。关于停顿时间的设置并不是越短越好。设置的时间越短意味着每次收集的CSet越小,导致垃圾逐步积累变多,最终不得不退化成Serial GC;停顿时间设置的过长,那么会导致每次都会产生长时间的停顿,影响了程序对外的响应时间。

堆布局

G1 将堆分区为一组大小相等的堆区域,每个堆区域都是一个连续的虚拟内存范围,如图 9-1 所示。区域是内存分配和内存回收的单位。在任何给定时间,这些区域中的每一个都可以为空(浅灰色),也可以分配给特定的一代,年轻或年老。当内存请求传入时,内存管理器会分发空闲区域。内存管理器将它们分配给一代,然后将它们作为可用空间返回到应用程序,它可以在其中分配自身。

图 7-1 G1 垃圾回收器堆布局

年轻一代包含伊甸园区域(红色)和幸存者区域(红色带"S")。这些区域提供与其他收集器中各自的连续空间相同的功能,不同之处在于,在 G1 中,这些区域通常在内存中以不连续的模式布局。旧区域(浅蓝色)组成了老一代。对于跨越多个区域的对象,旧一代区域可能是巨大的(浅蓝色带"H")。

应用程序始终分配给年轻一代,即伊甸园区域,但直接分配给属于老一代的巨大对象除外。

垃圾回收周期

在高电平上,G1集电极在两相之间交替。仅限年轻阶段包含垃圾回收,这些垃圾回收会逐渐用旧一代中的对象填满当前可用的内存。空间回收阶段是G1除了处理年轻一代之外,还逐步回收老一代的空间。然后,循环以仅年轻阶段重新开始。

图 9-2 概述了此循环,并举例说明了可能发生的垃圾回收暂停序列:

以下列表详细描述了 G1 垃圾回收周期的各个阶段、暂停以及各个阶段之间的过渡:

  1. 仅限年轻代的阶段:这个阶段从一些普通的年轻代开始,这些系列将对象推广到老年代。仅年轻阶段和空间回收阶段之间的过渡在老年代占用达到特定阈值(起始堆占用阈值)时开始。此时,G1 计划并发启动年轻集合,而不是普通年轻集合。

    • 并发启动 :此类型的集合除了执行普通年轻集合外,还会启动标记过程。并发标记确定在下一个空间回收阶段保留的旧一代区域中所有当前可访问的(活动)对象。虽然收集标记尚未完全完成,但可能会出现正常的年轻代收集。标记以两个特殊的停止世界暂停结束:备注和清理。

    • 备注:此暂停将完成标记本身,执行全局引用处理和类卸载,回收完全空白的区域并清理内部数据结构。在备注和清理之间 G1 计算信息,以便以后能够同时回收选定的旧生成区域中的可用空间,这将在清理暂停中完成。

    • 清理:此暂停确定是否实际进入空间回收阶段。如果随后是空间回收阶段,则仅幼崽阶段将以单个"准备混合幼崽"集合完成。

  2. 空间回收阶段:此阶段由多个混合集合组成,除了年轻一代区域外,还疏散旧一代区域集合的活体。当G1确定撤离更多的老一代区域不会产生足够的可用空间值得付出努力时,空间回收阶段就结束了。

空间回收后,收集周期以另一个仅限年轻人的阶段重新开始。作为备份,如果应用程序在收集活动信息时内存不足,G1 会像其他收集器一样执行就地停止世界完整堆压缩 (Full GC)。

垃圾回收暂停和收集组

G1 在停止世界暂停时执行垃圾回收和空间回收。活动对象通常从源区域复制到堆中的一个或多个目标区域,并调整对这些移动对象的现有引用。

对于非大型区域,对象的目标区域由该对象的源区域确定:

  • 年轻代的对象(伊甸园和幸存者区域)根据年龄被复制到幸存者或旧区域。
  • 旧区域中的对象将复制到其他旧区域。

巨大区域中的物体处理方式不同。G1只决定它们的存活度,如果它们不存活,则回收它们占用的空间。G1永远不会移动巨大区域内的物体。

收集组是要从中回收空间的源区域集。根据垃圾回收的类型,收集组由不同类型的区域组成:

  • 在仅限年轻代,收集组仅包含年轻一代中的区域,以及具有可能被回收的对象的巨大区域。
  • 在空间回收阶段,收集组由年轻一代中的区域、具有可能被回收的对象的巨大区域以及收集组候选区域中的一些旧一代区域组成。

G1 在并发周期内准备收集组候选区域。在备注暂停期间,G1 会选择占用率较低的区域,即包含大量可用空间的区域。然后,在备注和清理暂停之间同时准备这些区域,以供以后收集。清理暂停会根据其效率对此准备的结果进行排序。在后续混合集合中,似乎需要较少时间收集且包含更多可用空间的更高效区域是首选。

三、全局并发标记阶段

全局并发标记阶段是基于SATB的,与CMS有些类似,但是也有不同的地方,主要的几个阶段如下:

  • 初始标记阶段:G1 GC 在此阶段标记根。此阶段搭载在正常 (STW) 年轻垃圾收集之上。
  • 区域扫描阶段:G1 GC 扫描初始标记的幸存者区域以查找对旧一代的引用,并标记引用的对象。此阶段与应用程序(不是 STW)同时运行,并且必须在下一个 STW 年轻垃圾回收开始之前完成。
  • 并发标记阶段:G1 GC 查找整个堆中可访问的(活动)对象。此阶段与应用程序同时发生,并可能被 STW 年轻垃圾回收中断。
  • 备注阶段:此阶段是STW收集,有助于完成打标周期。G1 GC 会排出 SATB 缓冲区,跟踪未访问的活动对象,并执行引用处理。
  • 清理阶段:在最后阶段,G1 GC 执行记帐和 RSet 清理的 STW 操作。在记帐过程中,G1 GC 识别完全自由的区域和混合垃圾回收候选者。清理阶段在重置空区域并将其返回到空闲列表时部分并发。

四、G1特点

1. Java 堆大小调整

G1在调整Java堆大小时遵循标准规则,使用-XX:InitialHeapSize作为最小Java堆大小,-XX:MaxHeapSize为最大Java堆大小;-XX:MinHeapFreeRatio为最小可用内存百分比,-XX:MaxHeapFreeRatio确定调整大小后的最大可用内存百分比。G1收集器考虑在Remark和FullGC暂停期间调整Java堆的大小。此过程可以向操作系统释放内存或从操作系统分配内存。

1.1.年轻代调整

G1总是在下一个突变子阶段的正常年轻收集结束时确定年轻一代的大小。这样,G1可以满足基于实际暂停时间的长期观察使用-XX:MaxGCPauseTimeMillis和-XX:PauseTimeIntervalMillis设置的暂停时间目标。它考虑到了类似规模的年轻一代需要多长时间才能撤离。这包括在收集期间必须复制多少对象以及这些对象之间的互连程度等信息。

如果没有其他约束,则G1在-XX:G1NewSizePercent和-XX:G1MaxNewSizePpercent确定的值之间自适应地调整年轻一代的大小,以满足暂停时间。有关如何修复长暂停的更多信息,请参阅垃圾第一垃圾收集器调整。

或者,可以使用-XX:NewSize与-XX:MaxNewSize组合来分别设置最小和最大年轻一代大小。

1.2.在空间回收阶段

在空间回收阶段,G1尝试在单个垃圾收集暂停中最大化旧一代中回收的空间量。年轻一代的大小设置为允许的最小值,通常由-XX:G1NewSizePercent确定。

在该阶段的每个混合集合开始时,G1从集合集合候选集合中选择一组区域以添加到集合集合。这套额外的旧一代区域由三部分组成:

  • 确保疏散进度的至少一组年老代区域。这组年老代区域由集合集合候选中的区域数量除以空间回收阶段的长度由-XX:G1MixedGCCountTarget确定。
  • 如果 G1 预测在收集最小集合后将有剩余时间,则收集组候选项中的其他旧生成区域。添加旧生成区域,直到预计使用剩余时间的 80%。
  • 一组可选的收集组区域,G1 在其他两个部件撤出后逐渐撤出,并且此暂停中还有时间。

2.定期垃圾回收

如果由于应用程序不活动而长时间没有垃圾收集,那么VM可能会长时间保留大量未使用的内存,而这些内存可以在其他地方使用。为了避免这种情况,可以使用-XX:G1PeriodicGCInterval选项强制G1执行常规垃圾收集。此选项确定G1考虑执行垃圾收集的最小间隔(毫秒)。如果自上一次垃圾收集暂停以来经过了这段时间,并且没有正在进行的并发循环,G1将触发其他垃圾收集,并可能产生以下影响:

  • 在"仅年轻"阶段:G1使用"并发开始"暂停启动并发标记,如果指定了-XX:-G1PeriodicGCInvokesConcurrent,则使用"Full GC"。
  • 空间回收阶段:G1 继续空间回收阶段,触发适合当前进度的垃圾回收暂停类型。

-XX:G1PeriodicGCSystemLoadThreshold选项可用于优化是否触发垃圾收集:如果JVM主机系统(例如,容器)上getloadavg()调用返回的平均一分钟系统负载值高于此值,则不会运行定期垃圾收集。

五、启用 G1

垃圾第一垃圾收集器是默认的收集器,因此通常不需要执行任何其他操作。您可以通过在命令行上提供-XX:+UseG1GC来显式启用它。

六、G1的收集模式:

YoungGC:收集年轻代里的Region

MixGC:年轻代的所有Region+全局并发标记阶段选出的收益高的Region

无论是YoungGC还是MixGC都只是并发拷贝的阶段。

分代G1模式下选择CSet有两种子模式,分别对应YoungGC和mixedGC:

YoungGC:CSet就是所有年轻代里面的Region

MixedGC:CSet是所有年轻代里的Region加上在全局并发标记阶段标记出来的收益高的Region

G1的运行过程是这样的,会在Young GC和Mix GC之间不断的切换运行,同时定期的做全局并发标记,在实在赶不上回收速度的情况下使用Full GC(Serial GC)。初始标记是搭在YoungGC上执行的,在进行全局并发标记的时候不会做Mix GC,在做Mix GC的时候也不会启动初始标记阶段。当MixGC赶不上对象产生的速度的时候就退化成Full GC,这一点是需要重点调优的地方。

七、触发Full GC的情况:

1. 调用 System.gc()

只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。

2. 未指定老年代和新生代大小,堆伸缩时会产生fullgc,所以一定要配置-Xmx、-Xms

3. 老年代空间不足

老年代空间不足的常见场景比如大对象、大数组直接进入老年代、长期存活的对象进入老年代等。

为了避免以上原因引起的 Full GC,应当尽量不要创建过大的对象以及数组。

除此之外,可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。

还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。

在执行Full GC后空间仍然不足,则抛出错误:java.lang.OutOfMemoryError: Java heap space

4. JDK 1.7 及以前的(永久代)空间满

在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静

态变量等数据。

当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。

如果经过 Full GC 仍然回收不了,那么虚拟机会抛出java.lang.OutOfMemoryError PermGen space

为避免以上原因引起的 Full GC,可采用的方法为增大Perm Gen或转为使用 CMS GC。

5. 空间分配担保失败

空间担保,下面两种情况是空间担保失败:

1、每次晋升的对象的平均大小 > 老年代剩余空间

2、Minor GC后存活的对象超过了老年代剩余空间

八、G1常用参数

参数/默认值 含义

|---------------------------------------|-------------------------------------------------------------------------------------------------|
| -XX:+UseG1GC | 使用 G1 垃圾收集器 |
| -XX:MaxGCPauseMillis=200 | 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到) |
| -XX:InitiatingHeapOccupancyPercent=45 | 启动并发GC周期时的堆内存占用百分比. G1之类的垃圾收集器用它来触发并发GC周期,基于整个堆的使用率,而不只是某一代内存的使用比. 值为 0 则表示"一直执行GC循环". 默认值为 45. |
| -XX:NewRatio=n | 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2. |
| -XX:SurvivorRatio=n | eden/survivor 空间大小的比例(Ratio). 默认值为 8. |
| -XX:MaxTenuringThreshold=n | 提升年老代的最大临界值(tenuring threshold). 默认值为 15. |
| -XX:ParallelGCThreads=n | 设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同. |
| -XX:ConcGCThreads=n | 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同. |
| -XX:G1ReservePercent=n | 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10. |
| -XX:G1HeapRegionSize=n | 使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb. |

九、重要默认值

G1 GC 是一个自适应垃圾回收器,其默认值使其无需修改即可高效工作。以下是重要选项及其默认值的列表。此列表适用于最新的 Java HotSpot VM,内部版本 24。您可以通过在 JVM 命令行上输入以下选项并更改设置来调整和调整 G1 GC 以满足您的应用程序性能需求。

  • -XX:G1HeapRegionSize=n

    设置 G1 区域的大小。该值将是 1 的幂,范围从 32MB 到 2048MB。目标是根据最小 Java 堆大小拥有大约 <> 个区域。

  • -XX:MaxGCPauseMillis=200

    设置所需最大暂停时间的目标值。默认值为 200 毫秒。指定的值不适合您的堆大小。

  • -XX:G1NewSizePercent=5

    设置要用作年轻一代大小的最小堆的百分比。默认值为 Java 堆的 5%。这是一个实验标志。有关示例,请参阅"如何解锁实验性 VM 标志"。此设置将替换该设置。此设置在 Java HotSpot VM(内部版本 23)中不可用。-XX:DefaultMinNewGenPercent

  • -XX:G1MaxNewSizePercent=60

    设置要用作年轻一代大小的最大值的堆大小百分比。默认值为 Java 堆的 60%。这是一个实验标志。有关示例,请参阅"如何解锁实验性 VM 标志"。此设置将替换该设置。此设置在 Java HotSpot VM(内部版本 23)中不可用。-XX:DefaultMaxNewGenPercent

  • -XX:ParallelGCThreads=n

    设置 STW 工作线程的值。将 n 的值设置为逻辑处理器的数量。的值与逻辑处理器的数量相同,最大值为 8。n

    如果逻辑处理器超过 5 个,则将 的值设置为大约 8/5 的逻辑处理器。这在大多数情况下都有效,但较大的 SPARC 系统除外,其中的值大约是逻辑处理器的 16/<>。n``n

  • -XX:ConcGCThreads=n

    设置平行标记线程的数量。设置为大约 1/4 的并行垃圾回收线程数 ()。n``ParallelGCThreads

  • -XX:InitiatingHeapOccupancyPercent=45

    设置触发标记周期的 Java 堆占用阈值。默认占用是整个 Java 堆的 45%。

  • -XX:G1MixedGCLiveThresholdPercent=65

    设置要包含在混合垃圾回收周期中的旧区域的占用阈值。默认入住率为 65%。这是一个实验标志。有关示例,请参阅"如何解锁实验性 VM 标志"。此设置将替换该设置。此设置在 Java HotSpot VM(内部版本 23)中不可用。-XX:G1OldCSetRegionLiveThresholdPercent

  • -XX:G1HeapWastePercent=10

    设置您愿意浪费的堆的百分比。当可回收百分比小于堆浪费百分比时,Java HotSpot 虚拟机不会启动混合垃圾回收周期。默认值为 10%。此设置在 Java HotSpot VM(内部版本 23)中不可用。

  • -XX:G1MixedGCCountTarget=8

    设置标记周期后混合垃圾回收的目标数量,以收集最多包含实时数据的旧区域。默认值为 8 个混合垃圾回收。混合集合的目标是在此目标数量范围内。此设置在 Java HotSpot VM(内部版本 23)中不可用。G1MixedGCLIveThresholdPercent

  • -XX:G1OldCSetRegionThresholdPercent=10

    设置混合垃圾回收周期中要收集的旧区域数的上限。缺省值为 Java 堆的 10%。此设置在 Java HotSpot VM(内部版本 23)中不可用。

  • -XX:G1ReservePercent=10

    设置要保持空闲的保留内存的百分比,以降低空间溢出的风险。默认值为 10%。增加或减少百分比时,请确保将 Java 堆总量调整相同的量。此设置在 Java HotSpot VM(内部版本 23)中不可用。

相关推荐
seasugar4 分钟前
Maven怎么会出现一个dependency-reduced-pom.xml的文件
xml·java·maven
一只淡水鱼667 分钟前
【mybatis】基本操作:详解Spring通过注解和XML的方式来操作mybatis
java·数据库·spring·mybatis
pianmian111 分钟前
完全平方数
数据结构·算法
A_Tai233333313 分钟前
贪心算法解决用最少数量的箭引爆气球问题
算法·贪心算法
唐叔在学习24 分钟前
【唐叔学算法】第19天:交换排序-冒泡排序与快速排序的深度解析及Java实现
java·算法·排序算法
_nirvana_w_24 分钟前
C语言实现常用排序算法
c语言·算法·排序算法
music0ant28 分钟前
Idea 配置环境 更改Maven设置
java·maven·intellij-idea
唐叔在学习32 分钟前
【唐叔学算法】第18天:解密选择排序的双重魅力-直接选择排序与堆排序的Java实现及性能剖析
数据结构·算法·排序算法
记得开心一点嘛43 分钟前
Nginx与Tomcat之间的关系
java·nginx·tomcat
界面开发小八哥1 小时前
「Java EE开发指南」如何用MyEclipse构建一个Web项目?(一)
java·前端·ide·java-ee·myeclipse