G1垃圾回收器

G1垃圾回收器将堆内存划分为Region,通过动态调整Region大小适应不同大小的对象,优化垃圾回收效率。它使用RSet记录跨代引用,并通过并发标记、初始标记、重新标记、清理和拷贝等步骤进行MinorGC和MixedGC,目标是实现低延迟。G1适用于大内存应用,但在小内存应用中可能不如CMS。优化建议包括合理设置MaxGCPauseMillis和G1MixedGCCountTarget等参数。
摘要由CSDN通过智能技术生成

1、最大堆大小

G1管理的最大堆大小为64G。每个Region的大小通过 -XX:G1HeapRegionSize 来设置,大小为 1~32MB ,默认最多可以有2048个Region,G1能管理的最大堆内存是 32MB*2048=64G 。

使用G1垃圾回收器最小堆内存应为 1MB*2048=2GB ,低于此值建议使用其它垃圾回收器。

2、Region大小

Region大小为 1~32MB ,具体取值有1MB、2MB、4MB、8MB、16MB、32MB,Region大小优化与大对象有关,当对象占用内存超过Region的一半时将被视为大对象。

被标记为大对象将不利于垃圾回收。

:如果堆空间(内存)大的时候,每次进行「垃圾回收」都需要对一整块大的区域进行回收,那收集的时间是不好控制的

而划分多个小区域之后,那对这些「小区域」回收就容易控制它的「收集时间」了。

从上一次我们聊CMS回收过程的时候,同样讲到了Minor GC,它是通过「卡表」(cart table)来避免全表扫描老年代的对象

因为Minor GC 是回收年轻代的对象,但如果老年代有对象引用着年轻代,那这些被老年代引用的对象也不能回收掉

同样的,在G1也有这种问题(毕竟是Minor GC)。CMS是卡表,而G1解决「跨代引用」的问题的存储一般叫做RSet

只要记住,RSet这种存储在每个Region都会有,它记录着「其他Region引用了当前Region的对象关系」

对于年轻代的Region,它的RSet 只保存了来自老年代的引用(因为年轻代的没必要存储啊,自己都要做Minor GC了)

对于老年代的 Region 来说,它的 RSet 也只会保存老年代对它的引用(在G1垃圾收集器,老年代回收之前,都会先对年轻代进行回收,所以没必要保存年轻代的引用)

G1 的分代模型

G1 也分为年轻代和年老代,但不是固定划分,而是每个 Region 根据运行情况动态划分。

G1 还有一个特殊的区域叫 Humongous,G1 将超过了一个 Region 容量一半的大对象,都存放在 Humongous 区域中,如果对象超过了 Region 大小,则存放在 N 个连续的 Humongous Region 中。G1 的大多数行为都把 Humongous Region 作为老年代的一部分来进行看待。

TAMS(Top at mark start)

为了保证垃圾回收过程中的同时 Region 也能够被使用,G1 为每一个 Region 设计了两个名为 TAMS 的指针,分别是 Previous TAMS(PTAMS)、Next TAMS(NTAMS)。在并发标记阶段开始前,TAMS 指针指向 Region 内占用内存的边界。在并发标记阶段中,G1 默认指针之上的对象为存活对象不去进行标记,而对象分配时,用户线程直接在指针之上分配。这就保证了扫描行为和对象分配互不干扰。

G1 如何判定 Region 的“价值”

G1 运行期间会收集每个 Region 的价值信息,比如回收耗时、记忆集的脏卡数量等,通过计算得出每个 Region 回收的性价比。G1 的停顿预测模型就是通过这些信息,找出在用户预期时间内获得更高回收收益的 Region 组合。

Remembered Sets

G1 堆中的每一个 Region 都有一份Rememberd Set,也叫RSet,它的作用就是为每一个 Region 记录哪些 Region 对其含有引用。

RSet 的更新需要线程同步处理,由于对象引用变更非常频繁,如果同步写卡表消耗非常大,所以通常会把更新信息存入队列中再异步更新 RSet,这个队列就叫Dirty Card Queue

G1 的垃圾回收过程

当 Eden 中无法分配对象时,触发 Young GC。

当年老代占比到达 45%时,等待下一次 Young GC 时进行并发标记。

并发标记结束后马上执行 Mixed GC。

当 Mixed GC 对内存的清理速度赶不上分配新对象的速度时触发 Full GC,G1 的 Full GC 将使用单线程(JDK11 后改为多线程)执行标记整理算法,所以耗时巨大。

G1 的 Young GC

触发时机

当 JVM 无法在 Eden 区分配对象时。

回收范围

Eden 区和 Survivor 区

运行过程(所有阶段均 STW)

1. 根扫描

将所有 Eden 区中的 GC Root 和 RSet 记录的外部引用作为扫描存活对象的入口。

2. 更新 RSet

通过 Dirty Card Queue 中的 card 更新 RSet,保证 RSet 能准确反应老年代对该 Region 是否存在引用。

3. 处理 RSet

将 Eden 区中被 RSet 指向的对象标记为存活对象。

4. 对象复制

判断存活对象的年龄,如果未达到“阈值”,则复制到一个 Surviver 区中,否则复制到 Old 区中。如果 Surviver 空间不够,则将部分对象直接复制到 Old 区中。

5. 处理引用

处理软引用、弱引用、虚引用等,最终清空全部 Eden 区。这时清理过的内存空间没有内存碎片。

G1 的 Mixed GC

触发时机

年老代占用空间超过整个堆的 45%(可通过参数-XX:InitiatingHeapOccupancyPercent进行设置)

事实上,并不会立刻触发,而且等待下一次 Young GC,同步进行初始标记步骤。

回收范围

被并发标记过的 Region,这些 Region 是 G1 通过价值测算动态选中的。

运行过程

1. 初始标记(Initial Marking)[STW]

标记 GC Roots 直接关联的对象,并修改 TAMS 指针的值。值得注意的是,这一阶段并不单独执行,而是在 Minor GC 时同步完成。所以实际上这个阶段没有额外停顿。

2. 并发标记(Concurrent Marking)

与用户线程并发执行,顺着 GC Root 递归标记。标记完成后,重新扫描 SATB 记录的有引用变动的对象。如果这时发现空的 Region 则直接将其清空。

3. 重新标记(Remark)[STW]

由于并发标记是并发执行,并发标记结束后,仍然存在少量的引用变动的对象,所以在这个阶段可以 STW 来处理这部分遗留的对象。并且开始计算所有 Region 的活跃度。

4. 清理(Clean Up)[STW]

根据用户期望的停顿时间来制定回收计划,选择全部是非存活对象的 Old 区和回收收益较高的 Region 加入回收集。清空记忆集。重置已经被清理的空的 Region(这一步是非 STW 的)。

5. 拷贝(Coping)[STW]

将回收集其中的存活对象复制到空的 Region 中,最后清空这些旧的 Region。

这个阶段的算法和 Young GC 完全一致,但默认分 8 次执行完成(可由参数-XX:G1MixedGCCountTarget 设置)。所以每次清理的回收集包括 Eden 区、Survivor 区和八分之一的 Old 区。低存活度(垃圾多)的 Region 清理的较快,所以会被 G1 优先回收。

混合回收并不一定要进行 8 次。有一个阈值-XX :G1HeapWastePercent(默认值为 10%),意思是允许整个堆内存中有 10%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于 10%,则不再进行混合回收。

优点

G1 相比较之前的垃圾回收器最大的变化是通过化整为零的思路,将堆分为若干个小的 Region 来减少 GC 的范围,从而达到“低延迟”的目的。

并且 G1 的垃圾回收过程采用标记复制的算法,避免了空间碎片化的问题。

缺点

1.内存占用较高,由于 G1 分区比 CMS 更多,每个 Region 都需要建立卡表。其中新生代对象变动频繁,又加大了卡表维护的成本。

2.G1 不仅需要通过写前屏障来更新卡表,还需要写后屏障来跟踪并发时的指针变化以实现快照搜索算法(SATB)。这样虽然相比增量更新算法能够减少并发标记和重新标记阶段的消耗,但是用户程序运行时的计算负载就高了。

3.G1 和 CMS 同样具有“并发回收”的能力,所以垃圾回收的速度如果跟不上用户创建新对象的速度,那么就会触发一个 Full GC 来获取更多内存。通常把期望停顿时间设置为一两百毫秒或者两三百毫秒会是比较合理的。

这里要提下的是,在G1还有另一个名词,叫做CSet。

它的全称是 Collection Set,保存了一次GC中「将执行垃圾回收」的Region。CSet中的所有存活对象都会被转移到别的可用Region上

最佳实践

1.不要设置年轻代大小年轻代大小应当由 G1 自行控制,设置为固定值将覆盖暂停时间目标

2.暂停时间目标不要过于严苛 G1 为了 Young GC 能够缩短时间需要减少 Eden 区的个数,那么 Young GC 就会更加频繁。Mixed GC 想要达到停顿目标就需要减少回收的垃圾数量,如果回收速度低于新对象分配速度将引起 Full GC。

3.CMS 和 G1 的选择目前在小内存应用上 CMS 的表现大概率仍然要会优于 G1,而在大内存应用上 G1 则大多能发挥其优势,这个优劣势的 Java 堆容量平衡点通常在 6GB 至 8GB 之间。

7 总结

在 GC 的选择上,同样是“没有银弹”,不同的收集器有着各自的特点和适用场景,即使是 Epsilon 也会在特定场合下发挥作用。我们应针对不同的业务特征和系统情况选择最合适的垃圾回收器,而不是一味求新。

1、堆内存

参数默认值说明优化建议
MaxGCPauseMillis200ms最大停顿时间
G1HeapRegionSize不设置时启发式推断
G1NewSizePercent5新生代最小百分比
G1MaxNewSizePercent60新生代最大百分比

2、新生代内存回收

参数默认值说明优化建议
ParallelGCThreads并行GC线程数,会根据CPU核数推断默认值
MaxTenuringThreshold15从新生代晋升到老年代年龄阈值
SurvivorRatio8Eden和一个Survivor的比例
TargetSurvivorRatio50Survivor区内存使用率,增大该值会降低到老年代概率
+G1EagerReclaimHumongousObjectstrue是否在YGC时回收大对象

3、混合回收

参数默认值说明优化建议
G1MixedGCCountTarget8值越大,收集老年代分区越少
G1OldCSetRegionThresholdPercent10表示一次最多收集10%的分区

https://shipilev.net/talks/devoxx-Nov2017-shenandoah.pdf

什么?面试官问我G1垃圾收集器? - 掘金

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
G1(Garbage First)垃圾回收是一种低延迟的垃圾回收,它可以在不影响应用程序吞吐量的情况下,有效地处理大量的内存垃圾。下面是G1垃圾回收的执行流程: 1. 初始标记(Initial Mark):该阶段的目标是标记所有的根对象,并且标记从根对象直接可达的对象。为了达到这个目的,G1垃圾回收会扫描所有的Java线程的栈,以及记录下所有的GC Root。 2. 并发标记(Concurrent Mark):在初始标记之后,G1垃圾回收会开始并发的标记所有从根对象可达的对象。这是一个并发的过程,不会阻塞应用程序的执行。 3. 最终标记(Final Mark):在并发标记之后,G1垃圾回收会再次暂停应用程序的执行,以完成所有未被标记的存活对象的标记。这个过程与初始标记是类似的。 4. 筛选回收(Live Data Counting and Evacuation):在最终标记之后,G1垃圾回收会计算每个区域中存活的数据量。然后,它会选定一些区域作为回收集(Collection Set),将这些区域中的存活对象复制到空闲的区域中,并将这些区域标记为可回收的。 5. 清除(Cleanup):在筛选回收之后,G1垃圾回收会开始清理所有被标记为可回收的区域。 需要注意的是,G1垃圾回收是一个全局垃圾回收,因此它不仅仅会处理单个堆区域的垃圾回收,而是会处理整个Java堆。同时,它还会根据应用程序运行的情况,动态地调整回收集的大小,以达到最佳的垃圾回收效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值