在 Java 垃圾回收(GC)的演进史上,G1(Garbage-First)收集器是一座里程碑式的存在。它由 HotSpot 团队历时多年研发,于 JDK7u4 中首次可用,JDK9 正式取代 CMS 成为默认垃圾收集器,并在 JDK10、11 中持续优化,最终成为 JDK14 后唯一推荐的老年代收集器(CMS 被移除)。
G1 的核心设计理念是 "垃圾优先"------ 优先回收垃圾最多、回收收益最高的内存区域,同时兼顾 "低停顿" 与 "高吞吐量",完美解决了 CMS 收集器的内存碎片、并发模式失败等痛点,成为大内存、高并发场景(如微服务、分布式系统)的首选。本文将从原理到实战,全面拆解 G1 收集器,结合通俗比喻与专业细节,让你彻底掌握这一核心 GC 技术。
一、G1 核心定位:什么是 G1 收集器?
1. 核心定义
G1 是 HotSpot 虚拟机中一款面向整堆的垃圾收集器 ,基于 "分代收集" 理论与 "区域化内存布局" 设计,融合了 "标记 - 复制" 与 "标记 - 整理" 算法的优点,核心目标是可预测的低停顿------ 允许开发者通过参数指定 GC 的最大停顿时间,G1 会自动调整回收策略,确保停顿时间不超标。
2. 通俗比喻
把 JVM 堆内存比作多个独立的 "小区"(Region) ,每个小区里既有 "新住户"(新生代对象)也有 "老住户"(老年代对象),GC 线程是垃圾分类回收团队:
- 其他收集器(如 CMS):按 "新生代 / 老年代" 整体回收,要么只清理新小区,要么只清理老小区,容易造成长时间停顿;
- G1 收集器:先统计每个小区的垃圾量(回收收益),优先清理垃圾最多、清理最快的小区,同时严格控制每次清理的小区数量,确保总停顿时间不超过预设阈值 ------ 就像回收团队先挑 "垃圾多、清理快" 的小区下手,既保证回收效率,又不影响居民(用户线程)正常生活。
3. 适用场景与核心优势
(1)适用场景
- 大内存场景:堆内存≥4G(推荐 8G~64G),G1 的分区设计能更好地利用多核 CPU;
- 响应时间优先的服务:如 Web 应用、微服务、电商核心系统,要求 GC 停顿时间控制在 100~200ms 内;
- 混合场景:既需要低停顿,又不能牺牲过多吞吐量(如兼顾用户体验与系统处理能力)。
(2)核心优势(对比 CMS)
| 优势 | 具体说明 |
|---|---|
| 无内存碎片 | 基于 "标记 - 整理"+"复制" 算法,回收后内存连续,无需担心大对象分配失败 |
| 停顿可预测 | 支持通过-XX:MaxGCPauseMillis指定最大停顿时间,G1 自动适配 |
| 整堆回收能力 | 无需搭配其他收集器,可独立管理新生代和老年代,简化配置 |
| 抗并发模式失败 | 分区设计 + 动态调整回收区域,大幅降低 "并发模式失败" 风险 |
| 大内存高效 | 堆内存越大,G1 的分区优势越明显,而 CMS 在大内存下性能退化严重 |
二、G1 核心原理:区域化内存布局 + 分代收集
1. 内存布局:打破 "物理分代",改为 "逻辑分代 + 物理分区"
这是 G1 与传统收集器(如 CMS、Parallel Old)的核心区别 ------G1 不再将堆内存物理划分为 "新生代 + 老年代",而是将整个堆划分为多个大小相等的独立区域(Region) ,每个 Region 的大小固定(1MB~32MB,需为 2 的幂次方),可通过-XX:G1HeapRegionSize参数设置(默认由 JVM 根据堆大小自动计算)。
(1)Region 的四种类型
每个 Region 在运行时扮演不同角色,逻辑上归属于新生代或老年代,物理上是独立的内存块:
- Eden Region(E 区):新生代区域,存放新创建的对象,对应传统收集器的 Eden 区;
- Survivor Region(S 区):新生代区域,存放 Minor GC 后存活的对象,对应传统收集器的 Survivor 区(S0/S1);
- Old Region(O 区):老年代区域,存放长期存活的对象,对应传统收集器的老年代;
- Humongous Region(H 区):巨型对象区域,存放大小超过 Region 一半的大对象(如超大数组、大集合),直接分配在 H 区,避免占用多个普通 Region 导致内存碎片。
(2)分代逻辑
- 新生代(逻辑):由多个连续或不连续的 Eden Region 和 Survivor Region 组成,默认占堆内存的 5%(可通过
-XX:G1NewSizePercent调整初始占比,最大不超过 60%,由-XX:G1MaxNewSizePercent控制); - 老年代(逻辑):由多个 Old Region 和 Humongous Region 组成,占堆内存的剩余部分;
- 动态调整:G1 会根据应用运行情况,动态调整新生代和老年代的 Region 数量,平衡回收效率与停顿时间。
2. 核心算法:标记 - 复制(局部)+ 标记 - 整理(全局)
G1 结合了两种算法的优点,实现 "无碎片 + 高效率":
- 局部回收(Minor GC / 混合回收):采用 "复制算法"------ 将存活对象从一个 Region 复制到另一个空 Region,确保回收后的 Region 内存连续;
- 全局回收(Full GC):采用 "标记 - 整理算法"------ 将所有存活对象向堆的一端移动,清理剩余内存,避免碎片。
3. 垃圾优先策略:基于 "回收收益" 的优先级排序
G1 的 "垃圾优先" 并非随机回收,而是基于回收收益(回收的垃圾量 / 回收耗时) 排序:
- 每个 Region 都有一个 "垃圾占比" 统计(即该 Region 中垃圾对象的比例);
- G1 维护一个 "优先级列表",按垃圾占比从高到低排序;
- 每次 GC 时,G1 根据预设的最大停顿时间(
-XX:MaxGCPauseMillis),选择优先级最高的一批 Region 进行回收,确保在规定时间内完成回收。
4. 关键技术:Remembered Set(记忆集)
由于 Region 是独立的内存块,对象可能跨 Region 引用(如 Old Region 的对象引用 Eden Region 的对象)。为了避免 GC 时全堆扫描,G1 引入了Remembered Set(RS) 技术:
- 每个 Region 都对应一个 Remembered Set,记录其他 Region 对该 Region 中对象的引用;
- 当 GC 回收某个 Region 时,只需扫描其 Remembered Set,即可找到所有外部引用,无需遍历整个堆;
- 维护 RS 的开销由 "写屏障" 承担 ------ 当对象引用发生变化时,通过写屏障自动更新对应的 RS。
三、G1 完整工作流程:5 个阶段 + 3 种 GC 模式
G1 的垃圾回收过程分为新生代 GC(Minor GC)、混合 GC(Mixed GC)、Full GC三种模式,核心流程包括 5 个关键阶段,其中大部分阶段支持并发执行,仅关键步骤触发短暂 STW。
1. 三种 GC 模式(核心区别)
| GC 模式 | 清理范围 | 触发条件 | 核心特点 |
|---|---|---|---|
| 新生代 GC(Minor GC) | 仅新生代 Region(E+S) | 新生代 Eden Region 满 | 采用复制算法,STW 时间短,频率高 |
| 混合 GC(Mixed GC) | 新生代 Region + 部分老年代 Region | 老年代使用率达到阈值(默认 45%) | 核心模式,并发标记 + 并行回收,停顿可控 |
| Full GC | 整堆所有 Region(E+S+O+H) | 混合 GC 后内存仍不足,或大对象无法分配 | 基于标记 - 整理算法,STW 时间长,需避免 |
2. 混合 GC 完整流程(核心重点)
混合 GC 是 G1 的核心工作模式,也是 "低停顿 + 高收益" 的关键,分为 5 个阶段,其中仅初始标记、最终标记、筛选回收需要 STW:
阶段 1:初始标记(Initial Mark)------ STW(短暂)
- 核心工作:标记 GC Roots 直接关联的对象(如虚拟机栈引用、静态变量引用),同时修改 TAMS(Next Top Mark Start)指针,确保后续用户线程并发运行时,能在空闲 Region 中创建新对象。
- 通俗理解:回收团队先快速标记 "小区门口直接可见的垃圾",同时划定 "新住户可入住的空小区",避免回收期间影响新对象分配。
- 特点:STW 时间极短(通常 10ms 内),多线程并行执行。
阶段 2:并发标记(Concurrent Marking)------ 并发(无 STW)
- 核心工作:从初始标记的对象出发,遍历整个堆的引用链,标记所有存活对象;同时统计每个 Region 的垃圾占比(回收收益),为后续筛选回收做准备。
- 通俗理解:回收团队与居民(用户线程)并行工作,逐一排查每个小区的存活对象,同时记录每个小区的垃圾量,排序优先级。
- 特点:与用户线程并发执行,耗时较长(数百 ms),但不影响应用响应;期间用户线程可继续创建对象,产生的新垃圾会被标记为 "浮动垃圾",留到下次 GC 清理。
阶段 3:最终标记(Final Mark)------ STW(短暂)
- 核心工作:修正并发标记期间因用户线程修改引用导致的标记偏差,将 Remembered Set Logs 中的数据合并到 Remembered Set 中,确保所有存活对象都被正确标记。
- 通俗理解:回收团队短暂清场,核对之前的标记结果,补充标记并发期间遗漏的存活对象,确保统计的垃圾量准确。
- 特点:STW 时间比初始标记稍长(通常几十 ms),支持多线程并行执行,效率高。
阶段 4:筛选回收(Live Data Counting and Evacuation)------ STW(可控)
- 核心工作 :
- 筛选:根据每个 Region 的垃圾占比(回收收益)和预设的最大停顿时间,筛选出一批优先级最高的 Region(新生代 + 部分老年代);
- 回收:采用 "复制算法",将筛选出的 Region 中的存活对象并行复制到空 Region 中;
- 清理:清空原 Region,标记为空闲 Region,供后续对象分配使用。
- 通俗理解:回收团队根据 "垃圾多、清理快" 的原则挑选小区,在规定时间内完成清理,将存活的居民转移到空小区,原小区变为空房。
- 特点 :STW 时间可控(不超过
-XX:MaxGCPauseMillis),并行执行回收,回收后内存连续无碎片。
阶段 5:并发清理(Concurrent Cleanup)------ 并发(无 STW)
- 核心工作:清理筛选回收阶段产生的空 Region,更新 Region 状态,为下一次 GC 做准备。
- 特点:与用户线程并发执行,无 STW,不影响应用运行。
3. 新生代 GC(Minor GC)流程
当新生代 Eden Region 满时,触发 Minor GC,仅清理新生代的 Eden 和 Survivor Region:
- 标记新生代中的存活对象;
- 将存活对象复制到空的 Survivor Region(或直接晋升到老年代 Region,若年龄达到阈值);
- 清空原 Eden 和 Survivor Region,STW 时间极短(通常几十 ms)。
4. Full GC 流程(需避免)
当混合 GC 后老年代仍无足够内存,或大对象无法分配连续 Region 时,触发 Full GC:
- 采用 "标记 - 整理" 算法,暂停所有用户线程(长时间 STW);
- 遍历整堆所有 Region,标记存活对象;
- 将所有存活对象向堆的一端移动,清理剩余内存;
- 特点:STW 时间长(秒级),严重影响应用响应,需通过调优避免。
四、G1 实战调优:参数配置与问题解决
1. 核心 JVM 参数(启用与基础配置)
(1)启用 G1 收集器(JDK9 + 默认,JDK8 需手动启用)
-XX:+UseG1GC # 启用G1收集器(JDK8必须添加,JDK9+默认启用)
(2)内存与分区配置
-Xms8g -Xmx8g # 堆内存初始/最大=8G(建议设为相同,避免动态调整)
-XX:G1HeapRegionSize=4m # 每个Region大小=4MB(可选,默认JVM自动计算,范围1M~32M)
-XX:G1NewSizePercent=5 # 新生代初始占堆比例(默认5%)
-XX:G1MaxNewSizePercent=60 # 新生代最大占堆比例(默认60%)
(3)核心调优参数(重点)
| 参数 | 作用 | 推荐配置 |
|---|---|---|
-XX:MaxGCPauseMillis |
指定 GC 最大停顿时间(默认 200ms) | 100~200ms(根据业务响应时间要求调整) |
-XX:G1ReservePercent |
预留内存比例,用于应对晋升失败(默认 10%) | 10%~15%(避免混合 GC 时晋升失败触发 Full GC) |
-XX:InitiatingHeapOccupancyPercent |
老年代使用率阈值,触发混合 GC(默认 45%) | 40%~50%(提前触发,避免内存不足) |
-XX:ParallelGCThreads |
GC 并行线程数(默认 = CPU 核心数) | 等于 CPU 核心数(充分利用多核资源) |
-XX:ConcGCThreads |
并发 GC 线程数(默认 = ParallelGCThreads/4) | CPU 核心数 / 4(平衡并发 GC 与用户线程 CPU 占用) |
-XX:G1HeapWastePercent |
允许的堆内存浪费比例(默认 5%) | 5%~10%(控制回收精度与停顿时间的平衡) |
2. 常见问题与解决方案
(1)混合 GC 停顿时间超标
现象 :GC 日志中混合 GC 的 STW 时间超过-XX:MaxGCPauseMillis设置的阈值(如 200ms),影响应用响应。
原因:
- 单次回收的 Region 数量过多,导致清理时间过长;
- 新生代占比过大(超过 60%),Minor GC 清理耗时增加;
- 并发标记阶段耗时过长,导致最终标记和筛选回收阶段压力增大。
解决方案:
- 降低
-XX:MaxGCPauseMillis(如从 200ms 调至 150ms),G1 会自动减少单次回收的 Region 数量; - 限制新生代最大占比(
-XX:G1MaxNewSizePercent=50),避免新生代过大; - 增加并发 GC 线程数(
-XX:ConcGCThreads=4,针对 8 核 CPU),提升并发标记效率; - 提前触发混合 GC(降低
-XX:InitiatingHeapOccupancyPercent=40),减少单次回收的垃圾量。
(2)频繁触发 Full GC
现象:应用运行中频繁触发 Full GC,STW 时间长,系统卡顿。
原因:
- 老年代使用率阈值设置过高(如 50% 以上),混合 GC 启动过晚,内存不足;
- 预留内存比例不足(
-XX:G1ReservePercent过小),新生代对象晋升时老年代无足够空间; - 大对象过多,超过 Region 大小的一半,直接分配到 Humongous Region,快速占满老年代。
解决方案:
- 降低
-XX:InitiatingHeapOccupancyPercent=40,让混合 GC 提前触发,避免老年代满; - 增大预留内存比例(
-XX:G1ReservePercent=15),为晋升对象预留足够空间; - 调整
-XX:G1HeapRegionSize,让大对象能适配 Region 大小(如大对象为 8MB,Region 设为 8MB); - 优化应用代码,减少大对象创建(如拆分大集合、分页查询大数据量)。
(3)新生代 GC 频率过高
现象:Minor GC 每秒触发多次,虽然单次停顿短,但累计停顿时间影响吞吐量。
原因:
- 新生代初始占比过小(
-XX:G1NewSizePercent=5),Eden Region 快速被占满; - 应用频繁创建短期存活的对象,导致 Eden Region 频繁溢出。
解决方案:
- 增大新生代初始占比(
-XX:G1NewSizePercent=10),减少 Minor GC 触发频率; - 优化应用代码,减少临时对象创建(如复用 StringBuilder、使用对象池);
- 调整
-XX:MaxTenuringThreshold(对象晋升老年代年龄阈值,默认 15),让短期存活对象在新生代多回收几次,减少晋升压力。
3. G1 日志分析示例
启用 G1 后,通过以下参数打印 GC 日志(简化版):
-Xloggc:/tmp/gc.log -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime
关键日志解读(混合 GC 示例):
# 初始标记(STW)
[GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0080000 secs]
[Parallel Time: 6.0 ms, GC Workers: 8]
[GC Roots Scanning: 1.0 ms]
[Marking: 3.0 ms]
[Concurrent Mark Start]
# 并发标记
[GC concurrent-mark-start]
[GC concurrent-mark: 120.0 ms] [Times: user=300.0 ms, sys=20.0 ms, real=120.0 ms]
# 最终标记(STW)
[GC pause (G1 Humongous Allocation) (young) (final-mark), 0.0200000 secs]
[Parallel Time: 15.0 ms, GC Workers: 8]
[Root Region Scan: 5.0 ms]
[Marking: 8.0 ms]
# 筛选回收(STW)
[GC pause (G1 Humongous Allocation) (mixed), 0.0500000 secs]
[Parallel Time: 45.0 ms, GC Workers: 8]
[Evacuation Time: 40.0 ms] # 复制存活对象时间
[Region Cleanup: 3.0 ms]
[Heap Usage: Before GC: 6144M/8192M, After GC: 3072M/8192M] # 堆使用情况
# 并发清理
[GC concurrent-cleanup-start]
[GC concurrent-cleanup: 5.0 ms] [Times: user=10.0 ms, sys=1.0 ms, real=5.0 ms]
关键解读:
- 初始标记 STW:0.008 秒,最终标记 STW:0.02 秒,筛选回收 STW:0.05 秒,总 STW 时间 0.078 秒,符合
-XX:MaxGCPauseMillis=100ms的要求; - 并发标记耗时 120 秒(无 STW),堆内存从 6GB 降至 3GB,回收效果显著;
- 无内存碎片相关日志,说明回收后内存连续。
五、G1 与其他收集器的对比:如何选择?
| 收集器组合 | 核心优势 | 适用场景 | 缺点 |
|---|---|---|---|
| G1 | 低停顿、无碎片、可预测、大内存高效 | 大内存、响应时间优先的服务(JDK9 + 首选) | 配置稍复杂,小内存(<4G)场景优势不明显 |
| ParNew+CMS | 低停顿、配置简单 | JDK8 及以前的响应时间优先服务 | 内存碎片、并发模式失败风险高 |
| Parallel Scavenge+Parallel Old | 高吞吐量、配置简单 | 计算密集型、吞吐量优先的服务 | 停顿时间不可控,不适合 Web 应用 |
| ZGC/Shenandoah | 超低停顿(毫秒级)、超大内存支持 | 超大内存(≥64G)、极致低延迟场景 | JDK11 + 才支持,部分功能仍在优化 |
选择建议:
- JDK9+:优先使用 G1,无需额外配置,默认即可满足大部分场景;
- JDK8:若需低停顿,可选择 ParNew+CMS;若追求稳定性和无碎片,可启用 G1(需手动添加
-XX:+UseG1GC); - 超大内存(≥64G)+ 极致低延迟(<10ms):JDK11 + 可选择 ZGC/Shenandoah;
- 计算密集型、无响应时间要求:选择 Parallel Scavenge+Parallel Old,追求最大吞吐量。
六、G1 调优核心原则与最佳实践
1. 调优核心原则
- 停顿优先,兼顾吞吐量 :G1 的核心优势是低停顿,调优时优先保证
-XX:MaxGCPauseMillis达标,再通过调整并发线程数、回收阈值等优化吞吐量; - 避免 Full GC:Full GC 是 G1 的 "性能杀手",调优的核心目标之一是通过合理配置,让混合 GC 能及时回收老年代,避免触发 Full GC;
- 适配硬件资源 :GC 线程数(
ParallelGCThreads、ConcGCThreads)应根据 CPU 核心数调整,避免 GC 线程与用户线程争夺 CPU 资源; - 不盲目调参:G1 的默认参数已适配大部分场景,非必要不修改,仅在出现性能问题时针对性调优。
2. 最佳实践
- 堆内存配置 :
-Xms与-Xmx设为相同值,避免 GC 时动态扩展内存的开销;堆内存建议≥4G,充分发挥 G1 的分区优势; - Region 大小选择 :默认由 JVM 自动计算(堆大小 / 2048),若存在大量大对象,可手动调整
-XX:G1HeapRegionSize,让大对象能适配 Region 大小; - 最大停顿时间设置:根据业务要求设置(如 Web 应用设为 100~200ms),不宜过小(如 < 50ms),否则会导致回收不充分,反而增加 GC 频率;
- 监控重点指标 :通过 Prometheus+Grafana、JVisualVM 等工具监控:
- GC 停顿时间(是否达标
MaxGCPauseMillis); - 混合 GC 触发频率(是否过于频繁);
- 老年代使用率(是否长期接近
InitiatingHeapOccupancyPercent); - Full GC 触发次数(应尽量为 0)。
- GC 停顿时间(是否达标