🗑️ G1 GC vs ZGC

深度对比漫画图鉴

从内存布局到底层算法 · 分镜式专业拆解

💡 核心定位差异

G1:主打「可预测停顿」的分代回收器,在吞吐量和停顿之间做平衡,是Java 11+默认GC,适合大多数业务场景。
ZGC:主打「极低停顿」的并发回收器,亚毫秒级暂停,支持TB级堆,适合大内存、低延迟要求的核心服务,Java 21引入分代后吞吐量大幅提升。

☕
JVM 工程师

📐 内存布局对比

G1 GC

Region 化分代设计

Eden
Eden
S0
S1
年轻代(动态大小)
Old
Old
Old
Old
Old
Old
Old
Old
老年代
Humongous 大对象区(>Region一半)
✅ 每个Region大小 1~32MB(2的幂)
✅ 逻辑分代,物理不连续
✅ 大对象直接进入老年代
ZGC

Region 化 + Java21 分代

Young
Young
Young
Young
年轻代(Java21+ 分代)
Old
Old
Old
Old
Old
Old
Old
Old
老年代
大/中/小 三种Region尺寸
✅ 支持 2MB~256MB 不同规格Region
✅ Java21 前逻辑不分代
✅ 支持 NUMA 感知优化

🎬 回收流程分镜对比

G1 四阶段流程
STW 停顿 ① 初始标记

标记GC Roots直接引用的对象,停顿时间很短,和年轻代大小相关

并发执行 ② 并发标记

遍历对象图标记存活对象,和应用线程并发运行;用SATB快照保证一致性

STW 停顿 ③ 最终标记

处理SATB剩余引用,完成最终标记;停顿时间较短,和堆大小无关

STW 停顿 ④ 筛选回收

根据停顿预测模型选择回收价值最高的Region,复制存活对象;停顿时间主要在这里

ZGC 三阶段流程
STW 停顿 ① 标记开始

标记GC Roots直接引用,停顿极短(<1ms),只和GC Roots数量相关

并发执行 ② 并发标记/并发移动

并发遍历标记,同时完成对象移动整理;读屏障保证并发安全,几乎所有工作都在这一步

STW 停顿 ③ 标记结束 / 重定位

完成最终标记,处理根重定位;停顿依然亚毫秒级,和堆大小完全无关

💡 核心差异

ZGC的对象复制整理也是并发的,G1的筛选回收是全停顿的;这是两者停顿时间差距的根本来源。

🔧 核心技术底层对比

G1 核心技术

1. SATB 快照算法

并发标记开始时拍下对象图快照,通过写屏障记录引用变化,保证标记期间的引用修改不影响结果准确性。

2. Remembered Set 记忆集

每个Region维护一份引用记录,记录其他Region指向本Region的引用;避免回收时扫描全堆,代价是额外内存占用(约5%)。

3. 停顿预测模型

根据历史回收数据计算每个Region的回收成本,在满足-XX:MaxGCPauseMillis目标的前提下,选择回收价值最大的Region组合。

4. 混合回收模式

每次回收同时回收年轻代和部分老年代Region,兼顾吞吐量和停顿时间,增量式回收避免全堆GC。

ZGC 核心技术

1. 染色指针 Colored Pointers

利用对象指针的高位4位存储对象元信息(标记位、重定位位等),直接从指针就能知道对象状态,不需要额外的标记位图。 这是ZGC并发整理的关键

2. 读屏障 Load Barrier

读取对象指针时触发屏障,检查指针染色位;如果对象已被移动则自动修正指针,对应用层完全透明,保证并发移动的正确性。

3. 并发整理 Compaction

回收过程中并发完成对象复制和内存整理,彻底解决内存碎片问题;这是G1做不到的,G1整理需要全停顿。

4. 分代ZGC(Java21)

引入年轻代分代回收,频繁回收年轻对象,大幅降低并发回收压力,吞吐量提升25%以上,同时保持亚毫秒停顿。

📊 关键性能指标对比

对比维度 G1 GC ZGC
典型停顿时间 10ms ~ 200ms(随堆增大而变长) < 1ms(亚毫秒级,与堆大小无关)
停顿决定因素 堆大小、存活对象数量、Region数量 GC Roots 数量(通常几千个)
最大支持堆大小 ~ 64GB(再大停顿不可控) ~ 16TB(官方标称)
吞吐量 高(约90%+),Java 21前占优 中高(约85%+),分代ZGC追平G1
内存碎片 少量碎片,Region复制缓解 几乎无碎片,并发完整整理
分代回收 原生完整分代 Java 21 正式引入分代
额外内存开销 约5%(Remembered Set) 约10~15%(染色指针、标记数据)
CPU 占用 中等 较高(并发线程多)
默认版本 Java 9 ~ Java 21 默认GC 需手动开启,Java 21 生产可用

🎯 选型决策建议

👉 选 G1 的场景

  • • 堆内存 8G 以内的常规服务
  • • 对吞吐量要求高于极致低延迟
  • • 业务系统、普通微服务、后台任务
  • • 不想调优,开箱即用的场景
  • • Java 8/11 老版本兼容场景

👉 选 ZGC 的场景

  • • 大内存服务(16G 以上堆)
  • • 核心链路、低延迟要求的接口
  • • 秒杀、交易、网关等对停顿敏感场景
  • • 几十上百GB堆的大数据服务
  • • Java 17+ 版本,追求极致响应

💡 一句话决策

8G以下用G1省心;16G以上追求低延迟上ZGC;Java21+大堆直接上分代ZGC。

⚙️ 核心调优参数

G1 关键参数

  • -XX:MaxGCPauseMillis=200 目标停顿时间
  • -XX:G1HeapRegionSize 设置Region大小
  • -XX:G1NewSizePercent 年轻代最小比例
  • -XX:G1MaxNewSizePercent 年轻代最大比例
  • -XX:InitiatingHeapOccupancyPercent 触发并发标记阈值

ZGC 关键参数

  • -XX:+UseZGC 启用ZGC
  • -XX:+ZGenerational 开启分代ZGC(Java21+)
  • -XX:ConcGCThreads 并发GC线程数
  • -XX:ZCollectionInterval 最小回收间隔
  • -XX:+UseNUMA 开启NUMA优化

❌ 常见误区澄清

✕

误区1:ZGC 是零停顿 GC

错。ZGC 依然有 STW 阶段(标记开始/结束),只是停顿时间极短(<1ms),且不随堆大小增长,不是完全没有停顿。

✕

误区2:ZGC 没有分代

过时了。Java 21 正式引入分代 ZGC,年轻代回收效率大幅提升,吞吐量已经追平 G1。

✕

误区3:ZGC 一定比 G1 好

错。小堆(8G以下)场景下 G1 表现更均衡,ZGC 有额外内存和CPU开销,优势发挥不出来。

✕

误区4:G1 停顿时间是固定的

错。MaxGCPauseMillis 是目标值不是保证值,堆过大、存活对象过多时停顿会超出目标。

📦 生产推荐配置示例

G1 通用配置(8G堆)

java -Xms8g -Xmx8g \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:InitiatingHeapOccupancyPercent=45 \
     -XX:+ParallelRefProcEnabled \
     -XX:+HeapDumpOnOutOfMemoryError \
     -jar app.jar

分代ZGC配置(32G堆 Java21)

java -Xms32g -Xmx32g \
     -XX:+UseZGC \
     -XX:+ZGenerational \
     -XX:+UseNUMA \
     -XX:ConcGCThreads=4 \
     -XX:+HeapDumpOnOutOfMemoryError \
     -jar app.jar

G1 GC vs ZGC 深度对比漫画图鉴 | 基于 JDK 21 最新规范