当前位置: 首页 > article >正文

JVM G1垃圾回收器详细解析

G1内存布局

Garbage First(简称G1)收集器摒弃了传统垃圾收集器的严格的内存划分,而是采用了基于Region的内存布局形式和局部回收的设计思路。
在这里插入图片描述
G1垃圾收集器把Java堆划分为2048个大小相等的独立的Region,每个Region大小取值范围为1-32MB,且必须为2的N次幂参数,可以通过-XX:G1HeapRegionSize设定,-XX:G1HeapRegionSize默认为0,此时Region的大小采用Java堆大小/2048的计算公式来确定,取值为最接近的2的N次幂数值。

Region有四种:Eden、survivor、Old、Humongous(用于存储超过1.5个region的大对象),空白表示未使用区域。

G1的特点

分代收集: G1依然属于分代垃圾回收器,它会区分年轻代和老年代,年轻代依然有Eden和Survivor,但是从堆的结构上看,年轻代和老年 代不再物理隔离,而是逻辑上的概念;不要求整个eden、survivor区连续的,也不再坚持固定大小和数量。
避免内存碎片: 有利于程序长时间运行,分配大对象时不会因为无法找到连续的内存空间而提前触发下一次GC。
可预测的停顿时间: G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾回收上的时间不得超过N毫秒,可以通过参数-XX:MaxGCPauseMillis设置。 由于分区的原因, G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要的时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。保证G1收集器在有限的时间内获取尽可能高的收集效率。

G1的回收阶段划分

年轻代回收(Young GC):仅回收年轻代(Eden和Survivor区)。年轻代通常包含新创建的对象,这些对象更有可能在短时间内变成垃圾。Young GC的执行过程相对较快,因为它只涉及新生代中对象的扫描和回收。
在Young GC过程中,Eden区和Survivor区的存活对象会被复制到另一个Survivor区或者晋升到老年代。这个过程是Stop-The-World(STW)的,意味着在回收过程中,应用程序的所有线程都会被暂停。但是,由于新生代中的可回收对象通常较少,因此这个暂停时间通常较短,对应用程序的性能影响也较小。
并发标记周期(Concurrent Marking Cycle):包含初始标记(Initial Mark)、并发标记(Concurrent Marking)、最终标记(Final Marking)和清理(Cleanup)阶段。并发标记周期完成后,才会触发混合回收(Mixed GC),该阶段会回收部分年轻代和老年代Region。

  • Mixed GC
    当老年代占用率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值则触发,回收所有的Eden区、Survivor区和部分Old区(根据期望的GC停顿时间和回收收益确定Old区垃圾收集的优先顺序)以及大对象区。
  • Full GC
    在Mixed GC 回收的内存不够的时候触发Full GC,回收全堆。

Young GC的回收策略

Young GC过程中,如果老年代中有对象引用了年轻代中的对象,那么这些年轻代对象在回收过程中不应被错误地清除。Young GC面临一个挑战:如何识别和处理老年代对象对年轻代对象的引用?

厘清概念

记忆集(RememberedSet)

RSet详细记录了如老年代对象引用年轻代对象的关系。在年轻代回收时,RSet中的对象被临时加入到GC Root中,这样垃圾回收器就能够根据引用链准确地判断哪些对象需要回收,哪些对象由于被老年代引用而需要保留。
在这里插入图片描述
为了进一步优化内存使用,G1将每个区域中的内存按照一定大小划分成多个卡页(512B),并为每个卡页分配一个编号。在RSet中,不再记录单个对象的引用关系,而是记录对卡页的引用关系。这样,即使一个卡页中包含多个对象,也只需要记录一次卡页的引用,从而显著减少了内存开销。
在这里插入图片描述
RSet是一种points-into结构(谁引用了我),实际实现是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是卡表(Card Table)的Index。

卡表(Card Table)
G1为所有的Region维护了一张全局的卡表(Card Table),它的核心作用是记录跨代引用(老年代对象引用年轻代对象),是一种points-out(我引用了谁)的结构。卡表的每个卡(Card)通常是一个字节,标记为1表示“脏”,即该卡对应的内存块(512B)有老年代对象引用了年轻代对象。

  • 写屏障(Write Barrier):当发生跨代引用时,写屏障会触发卡表的标记操作,将对应的卡标记为“脏”。
  • RSet的更新:G1的每个Region的RSet通过卡表的脏卡信息,间接记录哪些Region的哪些卡页引用了当前Region的对象。记忆集的粒度更精细,避免全量扫描老年代(避免全量扫描在卡表中被标记为脏卡的卡页中的所有对象)。

Young GC的详细步骤

Young GC过程是Stop-The-World(STW)的。

1.GC Root扫描: 暂停应用线程,标记GC Root直达的对象。
2.处理脏卡:检查卡表中的脏卡,更新记忆集。
3.标记存活对象: 基于GC Root和记忆集的信息,G1会递归遍历所有被引用的对象,标记为存活,未被标记的对象即为垃圾。
4.选择回收集合: 根据停顿时间目标(-XX:MaxGCPauseMillis),动态计算哪些Region回收性价比最高,优先回收。

Mixed GC的详细步骤

上文提到,并发标记周期(Concurrent Marking Cycle)完成后,才会触发Mixed GC,并发标记周期如下所示。
在这里插入图片描述
1.初始标记(Initial Mark,STW)

这一阶段标记了从GC Root开始直接可达的对象。
G1垃圾回收器采用了三色标记法来识别对象的状态。

  • 黑色:当前对象不仅在GC Root的引用链上,而且它所引用的所有对象也已经被标记为存活。在位图中,相应的bit位被标识为1。
  • 灰色:当前对象在GC Root的引用链上,但其引用的其他对象尚未被标记。灰色对象不会直接体现在位图中,而是被放入一个专门的队列中,等待后续处理。
  • 白色:当前对象不在GC Root的引用链上,可被回收。在位图中,相应的bit位被标识为0。
    在这里插入图片描述

2.并发标记(Concurrent Marking)
这一阶段处理在初始标记阶段中放入灰色队列中的对象。
示例如上图和下图所示:从灰色队列中提取出对象B,并对其关联的A和C对象进行标记,A对象并未引用其他任何对象,因此可以立即标记为黑色,C对象引用了另一个对象E,因此C对象被标记为灰色,被放入灰色队列中等待进一步处理。同时,B对象已完成了对其所有引用对象的标记,因此也将B对象标记为黑色…下一轮标记和上面是一样的逻辑,直至灰色队列为空。

在这里插入图片描述
然而,三色标记法存在一个潜在的问题,即用户线程可能同时修改对象的引用关系,导致标记结果出现错误,如下图所示。
在这里插入图片描述
G1为了解决这个问题,使用了SATB算法(Snapshot At The Beginning, 初始快照)。
SATB算法的核心思想是在并发标记过程的起始阶段捕捉对象图的“逻辑快照”,并基于这个快照来进行后续的标记工作。在这个快照之后新生成的对象,会被直接标记为黑色,表示它们是活跃的,不应该被回收(可能会产生浮动垃圾)。
为了处理在标记过程中可能发生的对象引用变化,SATB算法采用了前置写屏障技术。这种技术会在引用赋值操作(B.c = new C()B.c = null)前触发,目的是捕获可能被删除的旧引用放入SATB待处理队列中。每个线程都有自己的SATB队列,但最终这些队列会被汇总到一个全局SATB队列中。

3.最终标记(Remark,STW)

后台的并发标记线程从全局SATB队列中取出对象,将其重新标记为灰色,并重新扫描其引用,将其子对象被递归标记为黑色。

4.清除垃圾(Cleanup)

依赖并发标记周期产生的数据,进行Mixed GC,回收所有Young区和部分Old区(对Old区的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划。

参考资料

https://segmentfault.com/a/1190000044966735
https://www.cnblogs.com/meixiaoyu/articles/16672125.html
https://www.bilibili.com/opus/662248429116719137
https://developer.aliyun.com/article/1502777


http://www.kler.cn/a/577160.html

相关文章:

  • Spring Boot项目 提示java: 程序包com.alibaba.druid.pool不存在
  • 【定制开发】碰一碰发视频系统定制开发,支持OEM
  • 【halcon】如何理解 halcon 中的domain 之 “区域被裁剪掉了!”
  • 【2025】Electron + React 架构筑基——从零到一的跨平台开发
  • SyntaxError: Invalid or unexpected token in JSON at position x
  • Trae 是一款由 AI 驱动的 IDE,让编程更加愉悦和高效。国际版集成了 GPT-4 和 Claude 3.5,国内版集成了DeepSeek-r1
  • 高性能算法NGO!北方苍鹰优化算法(Northern Goshawk Optimization,NGO)
  • 用低代码平台集成人工智能:无需专业开发也能实现智能化
  • 虚拟路由冗余协议(VRRP)技术详解:原理、应用与配置实践
  • matlab散点图
  • Android原生gif动图加载AnimatedImageDrawable
  • 云计算市场迎来新局面:亚马逊AWS与微软Azure激烈竞争
  • LeetCode刷题--杨辉三角
  • 【C++设计模式】第十篇:外观模式(Facade)
  • swift -(5) 汇编分析结构体、类的内存布局
  • 《量子Java:从超导芯片到光子计算的编程革命》——解析Google量子AI中心的混合架构,揭秘如何用Java控制量子比特!
  • C++ 初始化列表:成员变量的 “快速入场券”
  • 内核编程七:Linux 内核日志的级别
  • 大白话CSS 优先级计算规则的详细推导与示例
  • DBeaver 25.0 社区版安装与数据库连接配置指南(Windows平台)