G1 垃圾收集器详解

5 篇文章 0 订阅
本文详细介绍了G1收集器的特点、核心概念如Region、SATB和RSet,以及其在内存分区、并发收集、停顿控制和GC类型方面的运作机制。重点讲解了G1如何实现高吞吐量和低延迟,以及关键参数设置和日志分析。
摘要由CSDN通过智能技术生成

Garbage First(简称G1)收集器开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。它是一款专门针对于拥有多核处理器和大内存的机器的收集器,在满足了GC响应时间的延迟可控的情况下,也会尽可能提高的程序的吞吐量。

G1收集器具备如下特性:

  1. 能与用户线程同时执行,完成并发收集
  2. GC过程会有整理内存的过程,不会产生内存碎片,并且整理空闲内存速度更快
  3. GC发生时,停顿时间可控,可以让程序更大程度上追求低延迟
  4. 追求低延迟的同时,尽可能会保证高吞吐量
  5. 对于堆的未使用内存可以返还给操作系统

核心概念

为了实现高吞吐、没有内存碎片及收集时间可控等功能,G1引入了一些新的核心概念,如堆内存分区Region,原始快照STABSnapshot-At-The-Beginning)、记忆集Remembered SetG1收集器将Region作为单次回收的最小单元,每次垃圾回收时根据用户设定允许的收集停顿时间,优先处理回收价值收益最大的Region。

堆内存划分

区别于固定大小以及固定数量的分代收集器,G1是把堆划分为多个大小相等的独立区域(Region)。每个Region根据需要可以扮演Eden/Survivor/Old/Humongous

每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围从1M到32M,且应为2的N次幂。如果不设定,那么G1会根据Heap大小自动决定

Region中特殊的Humongous区域,专门用来存储大对象(G1设定大小超过了一个Region容量一半的对象即为大对象)

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

Region堆内存划分

SATB

原始快照(SATB:Snapshot At The Beginning),是G1收集器解决并发标记阶段对象消失问题的算法。并发标记过程会以最初的对象图关系进行访问,就算并发标记过程中某个对象的引用信息发生了改变,G1会通过写前屏障记录原有的对象引用关系,依旧会按照最初的对象图快照进行标记。

G1为每一个Region设计了两个名为TAMS(Top at Mark Start)的指针,把Region中的一部分空间划分出来用于并发回收过程的新对象分配,并发回收时新分配的对象地址都必须要在这两个指针位置以上。G1收集器默认在这个地址以上的对象是被隐式标记过的,即默认它们是存活的,不纳入回收范围

Rset

Rset全称为Remembered Set,是辅助GC过程的一种结构,和Card Table一样同为记忆集的一种实现,是典型以空间换时间的工具。

还有一种数据结构也是辅助GC的:Collection Set(CSet),它记录了GC要收集的Region集合,集合里的Region可以是任意年代的。在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可。

逻辑上说每个Region都有一个RSet,RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)。而Card Table则是一种points-out(我引用了谁的对象)的结构,每个Card 覆盖一定范围的Heap(一般为512Bytes)。G1的RSet是在Card Table的基础上实现的:每个Region会记录下别的Region有指向自己的指针,并标记这些指针分别在哪些Card的范围内。

RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index

Rset & Region

下图表示了RSet、Card和Region的关系(出处):

Remembered Sets

上图中有三个Region,每个Region被分成了多个Card,在不同Region中的Card会相互引用,Region1中的Card中的对象引用了Region2中的Card中的对象,蓝色实线表示的就是points-out的关系,而在Region2的RSet中,记录了Region1的Card,即红色虚线表示的关系,这就是points-into。

在虚拟机运行期间,RSet中的引用关系靠post-write barrierConcurrent refinement threads来维护

Rset作用
  • Young GC时,只需要选定年轻代Region的RSet作为根集,这些RSet记录了old->young的跨代引用,避免了扫描整个年老代。
  • Mixed GC时,年老代中记录了old->old的RSet,young->old的引用由扫描全部年轻代Region得到,从从而避免了扫描全部年老代Region

GC类型

G1中主要存在YoungGCMixedGC以及FullGC三种GC类型

Young GC

G1对于整个堆空间所有的Region区不会在一开始就全部分配完,无论是新生代、幸存区以及年老代在最开始都是会有初始数量的,在程序运行过程中会根据需求不断增加每个分代区域的Region数量

G1 YoungGC并非说Eden区放满了就会立马被触发。Eden区被用完时,G1首先会大概计算一下回收当前的年轻代空间需要花费多少时间,如果回收时间远远小于参数-XX:MaxGCPauseMills设定的值,那么不会触发YoungGC,而是会继续为Eden区增加新的Region区用于存放新分配的对象实例。直至某次Eden区空间再次被放满并经过计算后,此次回收的耗时接近-XX:MaxGCPauseMills参数设定的值,那么才会触发YoungGC

Young GC

  1. Young GC只回收Eden区和Survivor
  2. G1收集器中的新生代收集,依旧保留了分代收集器的特性,当YoungGC被触发时,拷贝存活对象到新Survivor Region,年龄大于max threshold则进入老年代,并处理引用

Mixed GC

MixedGC(混合型GC),当整个堆中年老代的区域占有率达到参数-XX:InitiatingHeapOccupancyPercent设定的值后触发MixedGC,发生该类型GC后,会回收所有新生代Region区、部分年老代Region区(会根据期望的GC停顿时间选择合适的年老代Region区优先回收)以及大对象Humongous区

正常情况下,G1垃圾收集时会先发生MixedGC,主要采用复制算法,在GC时先将要回收的Region中存活的对象拷贝至别的Region内,拷贝过程中,如果发现没有足够多的空闲Region承载拷贝对象,此时就会触发一次Full GC

Mixed GC

Full GC

当整个堆空间中的空闲Region不足以支撑拷贝对象或由于元数据空间满了等原因,会触发FullGC。G1首先会停止系统所有用户线程,然后采用单线程进行标记、清理和压缩整理内存,以便于清理出足够多的空闲Region来供下一次MixedGC使用

其实G1收集器中并没有FullGC,G1中的FullGC是采用Serial old FullGC。因为G1在设计时的初衷就是要避免发生FullGC,如果上述两种GC发生后还是无法使得程序恢复正常执行,最终就会触发SerialOld收集器的FullGC
最新版本jdk的G1 FullGC 是并行进行的了,在通用场景中的表现还优于 Parallel GC 的 Full GC 实现

垃圾回收过程

G1收集器一般在发生GC时执行过程大致会分为四个步骤(主要指MixedGC):

  1. 初始标记(InitialMark):先触发STW,然后使用单条GC线程快速标记GC Roots直连的对象,并修改TAMS指针的值 (借用Young GC时同步完成)
  2. 并发标记(ConcurrentMarking):从GC Roots开始对堆中对象进行可达性分析,标记要回收的对象。扫描完成后重新处理STAB记录下的在并发时有引用变动的对象
  3. 最终标记(Remark):纠正并发标记阶段因用户操作导致的错标、误标、漏标对象。
  4. 筛选回收(Cleanup):先对各个Region区的回收价值和成本进行排序,找出「回收价值最大」的Region优先回收。

G1收集器除了并发标记外,其余截断都要完全暂停用户线程

gc步骤

G1收集器的停顿预测模型是以衰减均值(Decaying Average)为理论基础来实现的,在垃圾收集的过程中,G1收集器会记录每个Region的回收耗时、每个Region记忆集里的脏卡数量等各个可测量的步骤花费的成本,并分析得出平均值、标准偏差、置信度等统计信息

G1 JVM参数

G1 GC相关的主要参数有:

参数含义
-XX:G1HeapRegionSize=n设置Region大小,并非最终值
-XX:MaxGCPauseMillis设置G1收集过程目标时间,默认值200ms,不是硬性条件
-XX:G1NewSizePercent新生代最小值,默认值5%
-XX:G1MaxNewSizePercent新生代最大值,默认值60%
-XX:ParallelGCThreadsSTW期间,并行GC线程数
-XX:ConcGCThreads=n并发标记阶段,并行执行的线程数
-XX:InitiatingHeapOccupancyPercent设置触发标记周期的 Java 堆占用率阈值。默认值是45% 这里的java堆占比指的是non_young_capacity_bytes,包括old+humongous
-XX:G1HeapWastePercentglobal concurrent marking结束之后,我们可以知道old gen regions中有多少空间要被回收,在每次YGC之后和再次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC
-XX: G1MixedGCLiveThresholdPercentold generation region中的存活对象的占比,只有在此参数之下,才会被选入CSet
-XX:G1MixedGCCountTarget一次global concurrent marking之后,最多执行Mixed GC的次数
-XX:G1OldCSetRegionThresholdPercent一次Mixed GC中能被选入CSet的最多old generation region数量
-XX:+UseStringDeduplicationJVM在GC时会做重复字符串消除

G1 GC日志分析

jdk17 G1 GC日志

Pause Young (Normal) (G1 Evacuation Pause) GC原因,Young GC
Heap before GC invocations=12555 (full 0): GC前对象引用计数
 garbage-first heap   total 2072576K, used 1874869K [0x0000000741800000, 0x0000000800000000) 使用了G1垃圾收集器,堆总大小2072576K,使用了1874869K
  region size 2048K, 759 young (1554432K), 3 survivors (6144K) Region大小为2M,青年代占用了759个(共1554432K),幸存区占用了3个(共6144K)
 Metaspace       used 85721K, committed 86592K, reserved 344064K 元数据取大小(没有设置MetaspaceSize=MaxMetaspaceSize,则会有committed和reserved)
  class space    used 8771K, committed 9280K, reserved 212992K
Using 4 workers of 4 for evacuation
Desired survivor size 265289728 bytes, new threshold 15 (max threshold 15)
  Pre Evacuate Collection Set: 0.3ms 预处理共耗时0.3ms,包括选择Region,扫描Root,更新添加Region到Collection Set等
  Merge Heap Roots: 0.2ms 扫描合并Rset
  Evacuate Collection Set: 2.6ms 拷贝存活对象到新region,年龄大于max threshold则进入老年代,并处理引用
  Post Evacuate Collection Set: 1.2ms 垃圾处理后操作,包括更新code root 引用,清除card table等
  Other: 0.1ms  其他事项共耗时0.1ms,其他事项包括选择CSet,处理已用对象,引用入ReferenceQueues,释放CSet中的Region到free list
Age table with threshold 15 (max threshold 15)
- age   1:    2223472 bytes,    2223472 total
- age   2:     719736 bytes,    2943208 total
- age   3:      60552 bytes,    3003760 total
- age   4:     278744 bytes,    3282504 total
- age   5:       1680 bytes,    3284184 total
- age   6:       5328 bytes,    3289512 total
- age   7:       1120 bytes,    3290632 total
- age   8:       2752 bytes,    3293384 total
- age   9:      64880 bytes,    3358264 total
- age  10:     138360 bytes,    3496624 total
- age  11:     186472 bytes,    3683096 total
- age  12:      12144 bytes,    3695240 total
- age  13:        336 bytes,    3695576 total
- age  14:       6368 bytes,    3701944 total
- age  15:      70752 bytes,    3772696 total
Eden regions: 756->0(757) 垃圾回收前后Eden区数量(Eden区总数为757)
 Used: 0K, Waste: 0K
Survivor regions: 3->2(253) 垃圾回收前后Survivor区数量(Survivor区总数为253)
 Used: 4072K, Waste: 23K
Old regions: 73->73
 Used: 147878K, Waste: 1625K
Archive regions: 2->2
 Used: 2016K, Waste: 2080K
Humongous regions: 85->7
 Used: 14336K, Waste: 0K
Metaspace: 85721K(86592K)->85721K(86592K) NonClass: 76949K(77312K)->76949K(77312K) Class: 8771K(9280K)->8771K(9280K)
Heap after GC invocations=12556 (full 0):
 garbage-first heap   total 2072576K, used 168302K [0x0000000741800000, 0x0000000800000000)
  region size 2048K, 2 young (4096K), 2 survivors (4096K)
 Metaspace       used 85721K, committed 86592K, reserved 344064K
  class space    used 8771K, committed 9280K, reserved 212992K
Pause Young (Normal) (G1 Evacuation Pause) 1832M->164M(2024M) 4.796ms 此次Young GC从1832M回收至164M,耗时4.796ms
User=0.02s Sys=0.00s Real=0.00s
Safepoint "G1CollectForAllocation", Time since last: 85999229627 ns, Reaching safepoint: 49914 ns, At safepoint: 4898699 ns, Total: 4948613 ns 安全点为G1CollectForAllocation

参考资料:

  1. 《深入理解Java虚拟机(第3版)》
  2. G1、ZGC、ShenandoahGC高性能收集器深入剖析
  3. G1垃圾收集器详解
  4. Java Hotspot G1 GC的一些关键技术
  5. Java 垃圾收集器与内存分配策略
  6. JVM GC 日志详解
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值