JVM学习笔记24-G1垃圾收集器

G1是一款面向服务端的垃圾收集器,适用于多核处理器和大内存环境,旨在提供可预测的停顿时间。G1将堆划分为多个Region,并通过高效的Copying算法避免内存碎片。相比CMS,G1能更好地平衡吞吐量和响应时间,通过预测模型选择回收Region。在运行过程中,G1包括Young GC和Mixed GC等模式,其中Mixed GC能同时回收年轻代和部分老年代,确保内存高效利用。G1使用RSet记录引用关系,通过SATB快照算法进行并发标记,实现高效垃圾收集。

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

G1收集器—Garbage First Collector
官方文档地址:点击访问

1 背景

  • 吞吐量
    吞吐量关注的是,在一个指定的时间内最大化一个应用的工作量
    如下方式来衡量一个系统吞吐量的好坏
    • 在一小时内同一个事务(或者任务, 请求)完成的次数(tps)
    • 数据库一小时可以完成多少次查询

对于关注吞吐量的系统,卡顿是可以接受的,因为这个系统关注长时间的大量任务的执行能力,单次快速的响应并不值得考虑

  • 响应能力
    响应能力—一个程序或者系统对请求是否能够及时响应,比如
    • 一个桌面UI能多块地响应一个事件
    • 一个网站能够多快返回查询的数据
    • 数据库能够多快的返回查询的数据

对于这列对相应能力敏感的场景,长时间的停顿是无法接受的


2 简介

  • G1收集器是一个面向服务端的垃圾收集器,适用于多核处理器,大内存容量的服务器
  • 它满足短时间GC停顿的同时达到一个较高的吞吐量
  • JDK 7以上版本适用

设计目标

  • 与应用线程同时工作,几乎不需要STW(与CMS类似)
  • 整理剩余空间,不产生内存碎片(CMS只能在Full GC时,用STW整理内存碎片)
  • GC停顿更加可控
  • 不牺牲系统的吞吐量
  • GC不要求额外的内存空间(CMS需要预留空间存储浮动垃圾)

设计规划—替换掉CMS
G1在某些方面弥补了CMS的不足,比如

  • CMS使用的是Mark-Sweep算法,自然会产生垃圾碎片
  • G1基于Copying算法,高效的整理剩余内存,而不要管理内存碎片

另外,G1提供多种手段,以达到对GC停顿时间的可控

  • Hotspot虚拟机的主要构成

Hotspot虚拟机的主要构成

3 G1收集器的堆结构

G1收集器的堆结构

  • 整个堆被划分为一个个相等的不连续的内存区域(regions),每个region都有一个分代的角色
    • Eden
    • Survivor
    • Old
  • 对每个角色的数量并没有强制的规定,也就是说对每个分代内存的大小,可以动态变化
  • G1最大的特点就是高效的执行回收,优先去执行那些大对象可回收的区域(region)

G1使用了GC停顿时间可预测的模型来满足用户设定的GC停顿时间,根据用户设定的目标时间,G1会自动的选择哪些region要清除,一次清除多少个region

G1从多个region中复制存活的对象,然后集中放入到一个region, 同时清除, 整理内存(copying收集算法)


4 G1对比其他垃圾收集器

  • 对比使用Mark-Sweep的CMS, G1使用的copying算法不会造成内存碎片
  • 对比Parallel Scavenge(基于copying), Parallel Old收集器(基于Mark-Compact ),Parallel会对整个区域做整理导致GC停顿时间会比较长,而G1只是特定的整理几个region

G1并非一个实时的收集器, 与Parallel Scavenge一样, 对GC停顿时间的设置并不绝对生效, 只是G1有较高的几率保证不超过设定的GC停顿时间.

与之前的GC收集器对比,G1会根据用户设定的GC停顿时间,只能评估哪几个region需要被回收可以满足用户的设定


6 相关概念

6.1分区

  • 分区(Region)—G1采取了不同的策略来解决并行, 串行和CMS收集器的碎片, 暂停时间不可控等问题----G1将整个堆分成相同大小的分区(Region)

每个分区都可能是年轻代也可能是老年代, 但是同一时刻只能属于某个代

年轻代, 幸存区,老年代这些概念还存在,成为逻辑上的概念, 这样方便复用之前分代框架的逻辑

在物理上不需要连续,则带来了额外的好处—有些分区垃圾对象特别多,有的分区垃圾对象很少,G1会优先回收垃圾对象特别多的分区,这也就是G1名字的由来,及首先收集拉结最多的分区

依然是在新生代满了的时候,对这个新生代进行回收—整个新生代中的对象,要么被回收,要么被晋升,至于新生代也采取分区机制的原因,则是因为这样跟老年代的策略统一,方便调整代的大小

G1还是一种带压缩的收集器,在回收老年代的分区时,是将存活的对象从一个分区拷贝到另外一个分区,这个拷贝就实现了局部的压缩


6.2 收集集合CSet

一组可被回收分区的集合

在Cset中存活的数据会在GC过程中被移动到另一个可用分区
Cset中分区可以来自Eden空间, Survivor空间或者老年代

6.3 已记忆集合RSet

RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)

Rset的价值在于使得垃圾收集器不需要扫描整个堆栈谁引用了当前分区中的对象,只需要扫描Rset即可

如下图所示
在这里插入图片描述
Region1和Region3中的对象都引用了Region2中的对象,一次在Region2的Reset中记录这两个引用

G1 GC是在points-out的card table之上再加了一层结构来构成points-into Rest—每个region会记录下到底哪些别的region有指向自己的指针,而这些指针分别在哪些card的范围内

这个Rest其实是一个hash table,key是别的region的起始地址,value是一个集合,里面的元素是card table的index.

举例来说,如果region A的Rset里有一项的key是region B,value里有index为1234的card
那么意思就是region B的一个card里有引用指向region A
所以对region A来说,该Rset记录的就是pints-into的关系;
而card table仍然记录points-out的关系

6.4 起始快照SATB(Snapshot-At-The-Beginning)

SATB是G1 GC在并发标记阶段使用的增量式的标记算法

并发标记是并发多线程的,但并发现在同一时刻只扫描一个Region

7 G1相对于CMS的优势

  • G1在压缩空间方面有优势
  • G1通过将内存空间分成区域(Region)的方式避免内存碎片问题
  • Eden, Survivor和Old区不在固定,在内存使用效率上来说更灵活
  • G1可以通过设置与其停顿时间(Pause Time)来控制垃圾收集时间,避免应用雪崩现象
  • G1在回收内存后会立即同时做合并空闲内存的工作,而CMS默认是在STW的时候做
  • G1会在Young GC中使用,而CMS只能在Old区使用

8 G1的适合场景

  • 服务端多核CPU, JVM内存占用较大的应用
  • 应用在运行过程中会产生大量内存碎片,需要经常压缩空间
  • 想要更可控,可预期的GC停顿周期:防止高并发下应用的雪崩现象

9 G1 GC模式

G1提供了两种GC模式—两者都是完全的STW

  • Young GC
    • 选定的所有年轻代里的Region
    • 通过控制年轻代的Region个数,及年轻代内存大小,来控制Young GC的时间开销
  • 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的

10 全局并发标记—global concurrent marking

其执行过程类似于CMS, 但是不同的是,在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必要环节

global concurrent marking的执行过程分为四个步骤

  • 初始标记(initial mark, STW)—它标记了从GC Root开始直接可达的对象
  • 并发标记(Concurrent Maring)
    这个阶段从GC Root开始对heap中的对象进行标记, 标记线程与应用线程并发执行, 并且收集各个Region的存活对象信息
  • 重新标记(Remarking STW)—标记那些在并发标记阶段发生变化的对象, 将被回收
  • 清理(Cleanup)—清除空Region(没有存活对象的), 加入到free list

其中

  • 第一阶段initial mark是共用了Young GC的暂停,这是因为他们可以复用root scan操作, 所以可以说global concurrent marking是伴随Young GC发生的

  • 第四阶段Cleanup只是回收了没有存活对象的Regin,所以它并不需要STW

11 G1在运行过程中的主要模式

  • YGC(不同于CMS)----Young GC
    在Eden充满时触发,在回收之后所有之前属于Eden的区块全部变成空包,既不属于任何一个分区(Eden, Survivor或Old)
  • 并发阶段
  • Mixed GC混合模式
    • 触发时机—由一些参数控制,另外也空着这哪些老年代Region会被选入Cset(收集集合)
      • G1HeapWastePercent
        在GCM结束之后,我们可以找到old gen regions中有多少空间要被回收,在每次YGC之后和每次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC
      • G1MixedGCLiveThresholdPercent
        old gen region中存活的对象的占比,只有在此参数之下,才会被选入Cset
      • G1MixedGCCountTarget
        一次GCM之后,最多执行Mixed GC的次数
      • G1OldCetRegionThresholdPercent
        一次Mixed GC中能被选入Cset的最多old gen region数量
  • Full GC(一般是G1出现问题时发生)

12 G1收集器概览

G1收集器将堆划分为若干个区域Region,它仍然属于分代收集器,不过,这些区域的一部分包含新生代

新生代的垃圾收集依然采用暂停所有应用线程的方式,讲存活对象拷贝到老年代或者Survivor空间

老年代也分成很多区域,G1收集器通过将对象从一区域负值到另外一个区域,完成了清理工作

这就意味着,在正常处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有CMS内存碎片的问题存在了

13 Humongous区域

在G1中,还有一种特殊的区域,叫Humongous区域

如果一个对象占用的空间达到或者是超过了分区容量的50%以上, G1收集器就认为这是一个巨型对象

这些巨型对象, 默认直接会被分配到老年代,但是如果他是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响

为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象
如果一个H区装不下一个巨型对象,那么G1会寻找连续的的H区来存储

为了找到连续的H区,有时候不得不启动Full GC

14 G1 Young GC

G1 Young GC主要是对Eden区进行GC,它在Eden空间耗尽时被触发

在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分会直接晋升到老年代空间

Survivor区中的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中,最终Eden空间的数据为空,GC完成后,应用线程继续执行

如果仅仅GC新生代对象,我们如果找到所有的跟对象呢?老年代所有的对象都是根么?那这样扫描下来会耗费大量的时间

于是,G1引进了Rset为概念—它的全称是Remember Set,作用是跟踪指向某个Heap区内的对象引用

在CMS中, 也有RSet的概念,在老年代中有一块区域用来记录指向新生代的引用—这是一种points-out,在进行Young GC扫描根时,仅仅需要扫描这一块区域,而不需要扫描整个老年代

但在G1中,并没有使用points-out,这是由于一个分区太小,分区数量太多,如果使用points-out的haul,会造成大量的空间浪费,有些根本不需要GC的分区引用也扫描了

于是G1中使用points-in来解决, points-in的意思是哪些分区引用了当前分区中的对象,这样,仅仅将这些对象当做根来扫描就避免了无效的扫描

由于新生代有多个,那么我们需要在新生代之间记录引用吗?这是不必要的原因在于每次GC时,所有新生代都会被扫描,所以只需要记录老年代到新生代之间的引用即可

14.1 Card Table—卡表

需要注意的是,如果引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1中又引入了另外一个概念,卡表Card Table

一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡

卡通常较小,介于128到512字节之间,

Card Table通常为字节数组,有Card的索引(即数组下标)来标识每个分区的空间地址

在默认情况下,每个卡都未被引用.当一个地址空间被引用时,这个地址空间对应的数组索引的值被标为’0’—标记为脏被引用

一般情况下,这个Rset其实是一个Hash Table, key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index

14.2 Young GC步骤

  • 阶段1: 根扫描

    • 静态和本地对象被扫描
  • 阶段 2: 更新RS

    • 处理dirty card队列更新RS
  • 阶段3: 处理RS

    • 检测从年轻代指向老年代的对象
  • 阶段4: 对象拷贝

    • 拷贝存活的读写到Survivor或Old区域
  • 阶段5: 处理引用队列

    • 软引用处理
    • 弱引用处理
    • 虚引用处理

15 Mixed GC—混合式GC

Mixed GC不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区

它的GC步骤分为两步

  • 全局并发标记—Global Concurrent Marking
  • 拷贝存活对象—Evacuation

G1到现在可以找到哪些老的分区可回收垃圾最多. 当全局并发标记完成后, 在某个时刻,就开始了Mixed GC

这些垃圾回收被称为"混合式"是因为他们不仅仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的分区

混合式GC也是采用复制清理策略,当GC完成后,会重新释放空间

16 G1分代算法

为老年代设置分区的目的是老年代里有的分区垃圾多,有的分区垃圾少,这样在回收的时候可以专注于收集垃圾多的区域,这也是G1名称的由来

不多这个算法并不适合新生代垃圾收集, 因为新生代的垃圾收集算法是复制算法,但是`新生代也是用分区机制主要是因为便于代大小的调整

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值