深度拆解 G1 GC 垃圾回收全过程:从 Region 到停顿控制的核心逻辑
G1 GC(Garbage-First Garbage Collector)作为 JDK 9 及以上版本的默认垃圾收集器,凭借 “兼顾低延迟与吞吐量”“无内存碎片”“支持大堆内存” 的特性,成为当前生产环境中最主流的 GC 选择。但多数开发者对 G1 的认知仅停留在 “分区管理”“优先回收垃圾多的区域” 等表层概念,对其完整回收流程、核心机制的细节知之甚少。
本文将聚焦 G1 GC 的垃圾回收全过程,从内存模型、触发条件到分阶段执行逻辑,结合实战细节和参数调优,帮你彻底搞懂 G1 GC 的工作原理,解决 GC 频繁、停顿过长等线上问题。
一、G1 GC 的核心基础:打破 “代” 的 Region 内存模型
要理解 G1 的回收流程,首先要掌握其最核心的内存设计 ——Region 分区模型,这是 G1 与传统分代收集器(如 CMS、Parallel GC)最本质的区别。
1.1 Region 的基本概念
G1 将整个 Java 堆内存划分为多个大小相等的独立内存块(Region),而非固定划分年轻代(Eden/Survivor)和老年代。
- Region 大小:由 JVM 根据堆内存总大小自动计算(范围 1MB~32MB,且为 2 的幂次方),也可通过
-XX:G1HeapRegionSize手动指定; - Region 类型:每个 Region 可动态标记为以下类型,且类型可随 GC 过程切换:
- Eden 区(E):存放新创建的对象;
- Survivor 区(S):存放年轻代 GC 后存活的对象;
- 老年代区(O):存放从年轻代晋升的长生命周期对象;
- 大对象区(Humongous,H):存放超过单个 Region 大小的大对象(会占用连续的多个 Region),直接划入老年代管理。
1.2 为什么要设计 Region?
传统分代收集器的年轻代 / 老年代是固定大小的连续内存,回收时需扫描整个代,停顿时间随内存增大而线性增加;而 G1 的 Region 模型:
- 回收时只需扫描 “垃圾占比高” 的 Region,而非整个堆,大幅缩短 STW 停顿;
- 动态调整各类型 Region 的数量,适配业务对象的生命周期(如秒杀场景临时创建大量短生命周期对象,G1 会自动增加 Eden 区 Region 数量);
- 回收时通过复制算法整理 Region,天然避免内存碎片(解决 CMS 的核心痛点)。
二、G1 GC 的垃圾回收类型:Young GC + Mixed GC + Full GC
G1 的回收流程分为三种类型,不同类型触发条件、执行逻辑不同,其中Young GC和Mixed GC是常规回收,Full GC是异常情况(需尽量避免)。
2.1 Young GC(年轻代 GC):只回收 Eden/Survivor 区
触发条件
当 Eden 区 Region 的总内存占用达到阈值(默认约 50%),触发 Young GC,仅回收年轻代 Region(E+S),不涉及老年代。
执行流程(核心是复制算法,全程 STW)
- 初始准备:暂停所有用户线程(STW),启动多线程 GC 任务;
- 标记存活对象:通过可达性分析,标记 Eden 区和 From Survivor 区的存活对象;
- 复制存活对象:将存活对象复制到空的 To Survivor 区(若 To Survivor 空间不足,部分对象直接晋升到老年代 Region);
- 清理与切换:清空原 Eden 区和 From Survivor 区,将 To Survivor 区标记为 From Survivor 区,等待下一次 Young GC;
- 恢复用户线程:STW 结束,用户线程继续执行。
关键特点
- 全程 STW,但仅回收年轻代 Region,停顿时间短(通常毫秒级);
- 线程数:默认等于 CPU 核心数,可通过
-XX:ParallelGCThreads调整; - 停顿目标:G1 会根据
-XX:MaxGCPauseMillis(默认 200ms)自动调整每次 Young GC 回收的 Region 数量,确保停顿不超过目标值。
2.2 Mixed GC(混合 GC):回收年轻代 + 部分老年代
Mixed GC 是 G1 的核心回收流程,也是 “Garbage-First”(优先回收垃圾多的区域)的体现,触发后同时回收年轻代和部分老年代 Region。
触发条件
老年代 Region 的内存占用达到 “并发标记阈值”(默认 45%,可通过-XX:InitiatingHeapOccupancyPercent调整),触发 Mixed GC,分为并发标记阶段和筛选回收阶段两大步骤。
第一步:并发标记阶段(几乎无 STW)
该阶段的核心目标是标记全堆的存活对象,并计算每个老年代 Region 的 “垃圾占比”(垃圾对象占 Region 总大小的比例),为后续筛选回收做准备。共分为 4 个子阶段:
| 阶段 | 是否 STW | 核心操作 | 耗时 / 特点 |
|---|---|---|---|
| 初始标记(Initial Mark) | 是(极短) | 标记 GC Roots 直接引用的对象(如虚拟机栈中的对象、静态变量) | 毫秒级,通常借 Young GC 的 STW 阶段完成,无额外停顿 |
| 并发标记(Concurrent Mark) | 否 | GC 线程与用户线程并行执行,遍历初始标记对象的引用链,标记全堆存活对象;同时计算每个老年代 Region 的垃圾占比 | 耗时最长(秒级),但不影响用户线程 |
| 最终标记(Final Mark) | 是(短) | 修正并发标记期间用户线程修改引用链导致的 “标记偏差”(如对象被创建 / 销毁、引用被修改) | 停顿时间比初始标记长,但远短于传统 Full GC |
| 清理筛选(Cleanup) | 否 | 筛选出垃圾占比最高的老年代 Region(优先回收列表),并清空无存活对象的 Region | 无 STW,为后续筛选回收做准备 |
第二步:筛选回收阶段(STW)
该阶段是 Mixed GC 的核心,体现 “Garbage-First” 的设计思想,全程 STW:
- 选择回收 Region:从 “优先回收列表” 中选择垃圾占比最高的一批 Region(包括年轻代 Region + 老年代 Region),数量由
MaxGCPauseMillis控制(确保停顿不超目标); - 复制存活对象:将选中 Region 中的存活对象复制到空 Region(年轻代存活对象复制到新 Survivor 区,老年代存活对象复制到新老年代区);
- 清理 Region:清空原 Region,标记为空闲,供后续对象分配使用;
- 恢复用户线程:STW 结束。
关键特点
- 每次 Mixed GC 仅回收 “垃圾占比高” 的老年代 Region(通常分批回收,而非一次回收所有),避免长时间 STW;
- 复制过程天然整理内存,无碎片;
- 并发标记阶段占用 CPU 资源(默认 GC 线程数为 CPU 核心数的 1/4),高 CPU 负载场景需调整线程数(
-XX:ConcGCThreads)。
2.3 Full GC(全堆 GC):G1 的 “兜底” 回收
Full GC 是 G1 尽量避免的回收方式,触发后会退化为串行收集器(Serial Old GC),全程 STW 且扫描整个堆,停顿时间极长(秒级甚至分钟级)。
触发条件
- Mixed GC 回收速度跟不上对象创建速度(如老年代内存快速占满);
- 大对象分配时无足够连续 Region;
- 并发标记阶段出现内存不足;
- JVM 参数配置错误(如
MaxGCPauseMillis设置过小,导致 G1 频繁回收,反而触发 Full GC)。
解决思路
Full GC 是线上问题的 “重灾区”,核心解决方法:
- 调整
InitiatingHeapOccupancyPercent(降低阈值,让 Mixed GC 提前触发); - 增大堆内存,避免内存不足;
- 优化代码,减少大对象创建;
- 调整
MaxGCPauseMillis(不要设置过小,建议 100~300ms)。
三、G1 GC 回收流程全解析:从触发到完成的完整链路
结合上述回收类型和阶段,我们用流程图梳理 G1 GC 的完整执行链路,并标注关键控制点:

核心细节补充
- 停顿时间控制:G1 在每次回收前会预测 “回收哪些 Region 能在
MaxGCPauseMillis内完成”,若预测停顿超时,会减少本次回收的 Region 数量; - Remembered Set(RS):G1 为每个 Region 维护 RS,记录其他 Region 对当前 Region 对象的引用,避免全堆扫描(这是 G1 能高效标记的关键);
- 卡表(Card Table):RS 的底层实现,将每个 Region 划分为 512 字节的 “卡”,当对象引用更新时,标记对应卡为 “脏卡”,并发标记时仅扫描脏卡,提升效率。
四、G1 GC 实战调优:核心参数与问题排查
理解回收流程后,结合线上常见问题,给出核心调优参数和排查思路。
4.1 核心调优参数
| 参数 | 作用 | 推荐值 |
|---|---|---|
-XX:+UseG1GC |
启用 G1 GC | 必设(JDK9 + 默认,JDK8 需手动设置) |
-XX:MaxGCPauseMillis |
设置 GC 最大停顿时间目标 | 100~300ms(不要过小,否则会导致 GC 频繁) |
-XX:G1HeapRegionSize |
指定 Region 大小 | 自动计算即可,无需手动设置 |
-XX:InitiatingHeapOccupancyPercent |
触发 Mixed GC 的老年代占用阈值 | 40~50(默认 45,内存紧张时设为 40) |
-XX:ParallelGCThreads |
Young GC/Mixed GC STW 阶段的线程数 | 等于 CPU 核心数(如 8 核设为 8) |
-XX:ConcGCThreads |
并发标记阶段的线程数 | CPU 核心数 / 4(如 8 核设为 2) |
-XX:G1NewSizePercent |
年轻代 Region 最小占比 | 5(默认 5,可根据业务调整) |
-XX:G1MaxNewSizePercent |
年轻代 Region 最大占比 | 60(默认 60,短生命周期对象多的场景可提高) |
4.2 线上常见问题排查
问题 1:Young GC 频繁触发
- 原因:Eden 区 Region 过小,对象创建速度快;
- 解决:提高
G1MaxNewSizePercent(如设为 70),增加年轻代 Region 数量;优化代码,减少临时对象创建。
问题 2:Mixed GC 停顿时间过长
- 原因:单次回收的老年代 Region 过多,或 RS / 卡表扫描耗时久;
- 解决:降低
MaxGCPauseMillis(如从 200ms 改为 100ms),减少单次回收的 Region 数量;检查是否有大量对象引用更新(如频繁修改集合),优化代码。
问题 3:频繁触发 Full GC
- 原因:Mixed GC 触发过晚,老年代内存不足;
- 解决:降低
InitiatingHeapOccupancyPercent(如从 45 改为 40),让 Mixed GC 提前触发;增大堆内存(-Xmx)。
4.3 GC 日志分析关键指标
开启 G1 GC 日志(-Xlog:gc*:file=gc.log:time,level,tags)后,重点关注以下指标:
[GC pause (G1 Young Generation) (young) 200M->100M(1024M), 0.010s]
[GC pause (G1 Mixed Generation) (mixed) 500M->200M(1024M), 0.050s]
- 第一行:Young GC,停顿时间 0.010s(10ms),堆内存从 200M 降到 100M;
- 第二行:Mixed GC,停顿时间 0.050s(50ms),符合
MaxGCPauseMillis=200ms的目标; - 若出现
[Full GC (G1 Full GC)],需立即排查内存不足或参数配置问题。
五、总结
G1 GC 的回收流程核心是 “分区管理 + 优先回收高垃圾占比 Region + 并发标记 + 复制整理”,总结关键要点:
- 内存模型:以 Region 为基本单位,动态划分年轻代 / 老年代,打破传统分代的固定边界;
- 回收类型:常规回收为 Young GC(仅年轻代,STW)和 Mixed GC(年轻代 + 部分老年代,低 STW),Full GC 为异常情况需避免;
- 核心优势:通过预测停顿时间、选择性回收 Region,兼顾低延迟和吞吐量,且无内存碎片;
- 调优核心:围绕
MaxGCPauseMillis和InitiatingHeapOccupancyPercent调整,避免 Full GC 触发。
掌握 G1 GC 的回收流程,不仅能帮你理解 GC 日志、解决线上 GC 问题,更能从内存角度优化代码(如减少大对象、临时对象创建),提升 Java 应用的稳定性和性能。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)