Java垃圾收集:Garbage First(G1)

2023年 7月 14日 64.3k 0

Garbage First(简称G1)收集器是垃圾收集器技术发展历史上的里程碑式的成果,基于Region的内存布局,面向整个Java堆进行垃圾收集,且用户可以指定自己预期的停顿时间。

在G1之前的垃圾收集器,一般只针对某个特定的分代(FULL GC时除外),要么面向新生代,要么面向老年代。虽然G1的设计也是基于分代收集的思想,但是由于使用了基于Region的内存布局,弱化了分代的概念,垃圾回收的衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大。进行垃圾回收时,G1会把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间.

G1视角下的堆内存布局

G1.png

G1之前的垃圾收集器,堆内存一般被分为新生代和老年代,年轻代又分为Eden区和两个Survivor区,不同分代和分区之间的空间大小比例通过参数指定。

Untitled.png

G1垃圾收集器将连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。

Region中有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为1MB~32MB,且应为2的N次幂。

对于那些超过了整个Region容量的超级大对象, 将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待。

Young GC与Mixed GC

G1收集器主要有 Young GC 和 Mixed GC两种模式。

Young GC

选定所有新生代里的Region。通过控制新生代的region个数,即新生代内存大小,来控制young GC的时间开销。(复制回收算法)
G1算法将堆划分为若干个区域(Region),它仍然属于分代收集器。不过,这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。老年代也分成很多区域,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。

Mixed GC

选定所有新生代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region。
Mixed GC不是full GC,它只能回收部分老年代的Region。如果mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(full GC)来收集整个GC heap。所以我们可以知道,G1是不提供full GC的。

SATB

SATB(Snapshot-At-The-Beginning),它是一种用于实现增量更新算法的技术,主要应用于 G1 垃圾收集器的并发标记阶段。在并发标记阶段,G1 需要在应用程序运行过程中进行内存对象的可达性分析。为了确保标记过程的正确性,G1 使用了 SATB 来在标记开始时获取堆内存中对象的快照。

SATB 使用了一种叫做 "写屏障"(write barrier)的技术来追踪对象之间的引用关系变化。当一个对象 A 的引用字段被修改为指向对象 B 时,写屏障会记录这个引用变化。通过分析这些记录,G1 可以在并发标记过程中确保不漏掉任何可达对象。这样,当标记阶段结束后,G1 就可以准确地找到所有的可达对象,从而回收不再使用的内存空间。

RSet

RSet(Remembered Set)是一种重要的数据结构。RSet 的主要作用是跟踪跨区域引用,以优化 G1 的增量收集策略。

G1 将堆内存划分为多个区域(region)。每个区域可以扮演新生代(Young Generation)、老年代(Old Generation)或是 Humongous 区域的角色。在 G1 中,垃圾回收的基本单位是区域,而不是整个堆。当进行垃圾回收时,G1 会优先回收垃圾对象最多的区域。为了实现这种增量收集策略,G1 需要有效地找到与目标区域有引用关系的其他区域。

这就是 RSet 发挥作用的地方。RSet 跟踪从其他区域指向特定区域的引用关系。每个区域都有一个对应的 RSet,其中包含从其他区域指向该区域内对象的引用信息。当进行垃圾回收时,G1 可以通过查找 RSet 快速找到与目标区域有引用关系的其他区域,从而避免全局扫描整个堆内存。

RSet 的维护是通过写屏障(write barrier)实现的。当一个对象 A 的引用字段被修改为指向对象 B 时,写屏障会检查 A 和 B 是否位于不同的区域。如果是,这个引用关系就会被添加到 B 所在区域的 RSet 中。这样,在进行垃圾回收时,G1 只需扫描与目标区域相关的 RSet,而不是整个堆。

RSet 是 G1 垃圾收集器中用于跟踪跨区域引用的数据结构,通过维护 RSet,G1 可以优化其增量收集策略,提高垃圾回收效率。

G1工作过程

Untitled 1.png

G1收集器的运作过程大致可划分为以下四个步骤:

  • 初始标记(Initial Marking)
  • 并发标记(Concurrent Marking)
  • 最终标记(Final Marking):
  • 筛选回收(Live Data Counting and Evacuation)

初始标记(Initial Marking)

  • 标记一下GC Roots能直接关联到的对象,
  • 修改TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。
  • 该阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。

并发标记(Concurrent Marking)

  • 从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。
  • 当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。

最终标记(Final Marking)

  • 对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
  • 增量更新:CMS收集器采用增量更新算法实现,而G1 收集器则是通过原始快照(SATB)算法来实现的

筛选回收(Live Data Counting and Evacuation):

  • 负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间制定回收计划,选择任意多个Region 构成回收集

  • 把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。

  • 这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的

  • G1收集器除了并发标记外,其余阶段也是要完全暂停用户线程的

可预测停顿时间模型

停顿时间模型的意思是能够支持指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间大概率不超过N毫秒这样的目标

可预测停顿时间模型的基本思路:

  • G1收集器在后台维护一个列表,记录Region里面的垃圾堆积的“价值”大小
  • 价值即回收所获得的空间大小以及回收所需时间的经验值
  • 每次根据用户设定允许的收集停顿时间,优先处理回收价值收益最大的那些Region
  • 使用参数-XX:MaxGCPauseMillis指定停顿时间,默认值是200毫秒

G1收集器的停顿预测模型:

  • G1收集器的停顿预测模型是以衰减均值(Decaying Average)为理论基础来实现的
  • 在垃圾收集过程中,G1收集器会记录每个Region的回收耗时、每个Region记忆集里的脏卡数量等各个可测量的步骤花费的成本,并分析得出平均值、标准偏差、置信度等统计信息。
  • 这里的“衰减平均值”是指它会比普通的平均值更容易受到新数据的影响,平均值代表整体平均状态,但衰减平均值更准确地代表“最近的”平均状态。

G1参数

Java G1 垃圾收集器(Garbage-First)提供了许多参数(JVM 参数),使得开发者可以根据应用程序的需求对其进行调优。以下是一些常用的 G1 相关参数:

  • 选择 G1 垃圾收集器:为了启用 G1 垃圾收集器,需要设置以下 JVM 参数:
-XX:+UseG1GC
  • 堆大小:通过以下参数设置 Java 堆的初始大小(Xms)和最大大小(Xmx):
-Xms -Xmx

例如,要将初始堆大小设置为 512 MB,最大堆大小设置为 4 GB,可以使用以下参数:

-Xms512m -Xmx4g
  • 新生代大小:使用以下参数设置新生代的大小(作为整个堆大小的百分比):
-XX:NewRatio=

其中 是一个整数,表示老年代与新生代的大小比例。例如,要将新生代大小设置为整个堆的 1/3,请使用以下参数:

-XX:NewRatio=2
  • 并发标记阈值:使用以下参数设置触发并发标记周期的堆占用百分比阈值:
-XX:InitiatingHeapOccupancyPercent=

例如,要在堆占用达到 45% 时触发并发标记,请使用以下参数:

-XX:InitiatingHeapOccupancyPercent=45
  • 最大暂停时间目标:通过以下参数设置 G1 垃圾收集的最大暂停时间目标(以毫秒为单位):
-XX:MaxGCPauseMillis=

例如,要将最大暂停时间目标设置为 200 毫秒,请使用以下参数:

-XX:MaxGCPauseMillis=200
  • 并行 GC 线程数:使用以下参数设置 G1 垃圾收集器的并行线程数:
-XX:ParallelGCThreads=

例如,要将并行 GC 线程数设置为 8,请使用以下参数:

-XX:ParallelGCThreads=8

这只是 G1 垃圾收集器参数的一部分。您可以根据应用程序的特点和需求进行调整。要查看所有可用的 G1 参数,可以使用以下命令:

java -XX:+UseG1GC -XX:+PrintFlagsFinal -version | grep G1

参考

开发者头条

Java Hotspot G1 GC的一些关键技术

JVM几种回收算法 CMS与G1的区别_懒虫虫~的博客-CSDN博客

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论