G1 GC 全过程与核心原理介绍

G1垃圾回收器通过年轻代和空间回收阶段交替进行内存管理,采用三色标记法避免漏标和错标。初始标记和回归标记确保对象正确标记,而清理阶段回收空间。当内存不足时触发Full GC,造成不可控的停顿。G1利用RSet和SATB解决并发问题,实现分Region高效回收。主要关注点在于减少Full GC的发生。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

G1GC 全过程与核心原理介绍

G1内存布局

G1垃圾回收器内存布局【链接:1
在这里插入图片描述

G1垃圾回收全过程
整体过程介绍在这里插入图片描述

G1垃圾回收整体分为2个阶段,分别是仅年轻代回收阶段(young-only)和空间回收阶段(space reclamation)。这两个阶段不断交替,相互转换。在年轻代回收阶段,每次回收都会有对象升级,当年龄达到阈值,就会变成老年代对象,放到old区。当old区占比达到一个设定的阈值,就会进入空间回收阶段,释放出空间,然后又进入年轻代回收。要进行一次空间回收,前提条件是找出空间中所有的垃圾,这需要经过3个主要的步骤,分别是初始标记、回归标记和最后的清理操作。

回收过程中的关键环节如下:

  1. 初始标记 [STW]
    1. 将gc root的一级子节点标记出来,作为后续并发标记的源头
  2. 回归标记 [STW]
    1. 在并发标记的过程中,对象的引用会发生改变,因此,在并发标记结束后,需要进行一次回归标记,从而更新整个堆中的对象状态。回归标记结束,整个垃圾标记的过程也就结束了。回归标记的具体原因,后面会再进行介绍。
  3. 清理 [部分STW]
    1. 当所有的垃圾被标记出来之后,G1首先会找出那些全是垃圾的region,直接回收,放入可用集合。此外,还会在这个阶段判断接下来是否需要真正执行空间回收的操作,如果回收的空间比较多,则会执行,如果能够回收的空间比较小,就不会执行了。
G1 Full GC

在上面的介绍中,G1主要进行年轻代回收和混合回收,这些造成的停顿整体是可控的。但是如果在对象清理的过程中,发生了内存不足,那么在OOM之前,G1会启动FullGC,会进入一个停顿,并且用单线程执行回收,此时造成的停顿是不可控的,一般来讲,G1调优的最主要的目标就是要避免FullGC的出现。

重点原理介绍
G1如何进行并发标记?

G1的标记算法为三色标记,示意图如下

img

进行并发标记的时候,由于对象的状态是不断变化的,如果不做特殊处理,就会出现两个问题,分别是漏标和错标。

  1. 当标记完的对象在清理之前变为了垃圾,就会出现错标,此时这些多出来的对象被称作浮动垃圾。浮动垃圾影响有限,顶多就是下次再回收。
  2. 如果某些对象不是垃圾,但是因为并发问题,没有标记到,则产生漏标问题,结果就非常严重,可能导致系统崩溃。

漏标问题示意图

img
G1如何解决漏标问题?

G1解决漏标问题的方案是采用初始快照算法,SnapShot-At-The-Beginning(SATB)。定义如下:在标记开始的时候,对整个堆空间拍一个快照,并且此次回收的对象,就以此次快照的内容为准。这样,虽然后续会有并发修改,但是不会改变初始快照的状态,从而解决了漏标问题。

不过这个快照是虚拟的,底层实现是通过一个写屏障+链表实现的。SATB引入了write barrier技术,每当应用线程将对象赋值给引用字段时,会回调写屏障的方法。(假设将字段f设置为null,则发生f=null的赋值,此时会回调写屏障),写屏障的逻辑是,将引用原来的内容记录到一个linked list中。

img

当并发标记结束,jvm会stop the world,然后处理这个SATB-list,对list中的对象以及他们的引用进行标记,当标记结束,结束STW,经过这样一个过程,就可以确定,所有在最开始存活的对象都被标记为活对象了。

G1如何实现分Region回收?

垃圾回收阶段最关键的问题是要回收的区域可能被别的区域引用,存活对象的地址发生改变,原来的引用需要重新指向新的内存地址

全堆扫描回收方案

参考【1

img
  1. 开启全堆扫描,如果扫到一个对象应用,并且这个引用指向待回收的region,则进行以下操作
    1. 如果这个被引用的对象没有完成拷贝,则执行这个对象的拷贝,在老对象头上标记已拷贝,更细当前的引用关系,而且把新地址记录在老对象头上。
    2. 如果某个对象被标记为已拷贝,则直接用记录下来的新地址,更新引用。
  2. 当扫描完成后,待回收region的可用对象就全部被拷贝完成,对应引用也更新完成。
基于Remembered Set扫描回收方案

参考【1

在这里插入图片描述

  1. 什么是RSet?
    1. JVM的虚拟内存被划分为多个Card,每个Region也由多个Card组成
    2. 在G1中,每个Region有一个对应的Rset,本质是一个2^n位的bitMap,每一个bit对应一个Card
    3. 最开始所有的bit状态都是clean,当某个Card指向了本Region中的对象,则这个Card对应的Rset bit,会被标记为dirty。
    4. 通过Rset,每个Region可以知道有哪些Card指向自己,方便后续的快速分Region回收。Rset的设计思路和倒排索引比较类似。
  2. G1如何基于RSet回收?
    1. 如果要回收Region2,首先扫描GC Root,找到被引用的对象,进行迁移。然后,扫描Region2的Rset,找到所有的Card,然后扫描其中的对象,迁移这些对象的引用。
  3. 如何维护Rset?
    1. G1维护Rset的核心是Post Write Barrier和Concurrent refinement threads
    2. 每次如果发现有跨区域引用,则会将这个引用放入到一个队列中,当队列达到一定的阈值,则启动refinement线程,更新RSet
    3. 如果Rset的产生过快,refinement线程不够用,则业务线程会帮助执行,这种情况要尽量避免
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值