Skip to content

ZGC 与 Shenandoah:低延迟收集器的新时代

当 G1 还在为可控的停顿时间努力时,ZGC 和 Shenandoah 已经将目标锁定在亚毫秒级停顿

这不仅仅是数字的进步,而是垃圾收集器设计的范式转变。


传统收集器的瓶颈

为什么 Stop The World 是问题?

┌─────────────────────────────────────────────────────────────┐
│  传统 GC 的停顿问题                                          │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  用户请求                                              │    │
│  │    │                                                  │    │
│  │    ▼                                                  │    │
│  │  ┌──────────────────────────────────────────────┐    │    │
│  │  │          Stop The World                       │    │    │
│  │  │                                              │    │    │
│  │  │    1 秒 ────────────────────────────────►   │    │    │
│  │  │                                              │    │    │
│  │  │  用户体验:卡顿 1 秒                          │    │    │
│  │  └──────────────────────────────────────────────┘    │    │
│  │    │                                                  │    │
│  │    ▼                                                  │    │
│  │  响应返回                                              │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                              │
│  传统 GC 停顿时间:100ms ~ 1000ms                            │
│  现代应用需求:< 10ms                                        │
│                                                              │
└─────────────────────────────────────────────────────────────┘

传统优化的极限

收集器停顿时间原理
Serial很长单线程
Parallel多线程
CMS短(并发)并发标记
G1可控Region 选择
理论极限~10ms传统设计无法突破

ZGC:停顿时间 < 1ms

ZGC 的核心设计

┌─────────────────────────────────────────────────────────────┐
│  ZGC:着色指针(Colored Pointers)技术                       │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  64 位系统,Java 对象引用使用 48-56 位                        │
│       │                                                      │
│       ├── 42 位:堆地址(最大 4TB)                         │
│       ├── 4 位:ZGC 标记状态(Marked0, Marked1, Remapped)   │
│       └── 18 位:保留                                         │
│                                                              │
│  关键洞察:                                                  │
│  - 标记状态存在指针本身,而不是对象头                         │
│  - 读取对象时,通过指针位运算判断状态                         │
│  - 永不修改对象头,只修改指针                                │
│                                                              │
└─────────────────────────────────────────────────────────────┘

ZGC 的三色标记 + 读屏障

java
// ZGC 的读屏障(Load Barrier)
public Object loadObject(Object obj) {
    Object o = obj.field;
    // 读屏障:检查指针的 Marked 状态
    // 如果对象被移动,自动修正引用(自愈)
    if (needsBarrier(o)) {
        return sanitize(o);  // 引用修正
    }
    return o;
}

ZGC 的并发阶段

┌─────────────────────────────────────────────────────────────────────┐
│                        ZGC 收集周期                                 │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐            │
│  │  并发标记   │───►│ 并发重定位  │───►│  并发预清理  │            │
│  │ Concurrent  │    │ Concurrent  │    │   Concurrent │            │
│  │   Mark     │    │  Relocate   │    │   Prepare   │            │
│  └─────────────┘    └─────────────┘    └─────────────┘            │
│       │                   │                   │                      │
│       ▼                   ▼                   ▼                      │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐            │
│  │ 初始标记    │    │ 最终重定位  │    │ 重新标记    │            │
│  │ (STW)很短   │    │  (STW)很短  │    │  (STW)很短  │            │
│  └─────────────┘    └─────────────┘    └─────────────┘            │
│                                                                      │
│  所有并发阶段:用户线程和 GC 线程同时运行                           │
│  STW 时间:&lt; 1ms                                                    │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

ZGC 的引用修正(自愈)

┌─────────────────────────────────────────────────────────────┐
│  ZGC 的自愈机制                                              │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  对象移动时:                                                │
│                                                              │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  旧地址  ──移动──► 新地址                            │  │
│  │     │                                                │  │
│  │     │  对象 A 引用旧地址                             │  │
│  │     ▼                                                │  │
│  │  ┌───────────────────────────────────────────────┐   │  │
│  │  │  读取 A 时触发读屏障                           │   │  │
│  │  │  检测到 A 的地址 是旧地址(Remapped)         │   │  │
│  │  │  自动修正引用到新地址                          │   │  │
│  │  │  返回新地址                                    │   │  │
│  │  └───────────────────────────────────────────────┘   │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                              │
│  多次读取后,所有引用都「自愈」到新地址                      │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Shenandoah:Oracle 的开源方案

与 ZGC 的区别

┌─────────────────────────────────────────────────────────────┐
│  ZGC vs Shenandoah 核心对比                                  │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  特性              ZGC                  Shenandoah            │
│  ─────────────────────────────────────────────────────      │
│  染色指针          ✓(硬件支持)        ✓(软件模拟)        │
│  支持版本          JDK 11+             JDK 12+               │
│  维护方            Oracle               Red Hat              │
│  分代支持          ZGC 15+ 支持        无(纯不分代)        │
│  堆上限            16TB                无限制                │
│  并发压缩          ✓                   ✓                      │
│  GC 线程数         自动                可配置                │
│                                                              │
│  共同点:                                                        │
│  - 都是亚毫秒级停顿                                            │
│  - 都是并发收集                                                │
│  - 都不分代(Shenandoah)或可选分代(ZGC 15+)             │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Shenandoah 的 Brooks Pointer

java
// Shenandoah 使用转发指针(Brooks Pointer)
// 对象头中增加一个指针,指向对象的新位置
public class BrooksPointer {
    // 对象头结构
    // [Brooks Pointer] [Mark Word] [Class Pointer] [Fields]
    //                  ↑                    ↑
    //              转发指针              指向当前对象位置

    // 读取对象时,检查转发指针
    public Object load(Object obj) {
        // 检查 Brooks Pointer 是否指向新位置
        if (obj.brooksPointer != obj) {
            // 对象已移动,更新引用
            obj = obj.brooksPointer;
        }
        return obj;
    }
}

性能对比

停顿时间对比

收集器Young GCMixed GCFull GC
Serial500ms-1000ms
Parallel200ms-500ms
CMS50ms-300ms
G120ms50ms100ms
Shenandoah< 1ms< 1ms< 1ms
ZGC< 1ms< 1ms< 1ms

吞吐量对比

┌─────────────────────────────────────────────────────────────┐
│  吞吐量 vs 停顿时间                                          │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  吞吐量                                                        │
│    ▲                                                         │
│    │                          ╭─ ZGC/Shenandoah              │
│    │                     ╭────╯                               │
│    │                ╭────╯  ╭─ G1                           │
│    │           ╭────╯  ╭───╯ ╭─ Parallel                    │
│    │      ╭────╯  ╭───╯ ╭───╯ ╭─ Serial                     │
│    │ ╭────╯  ╭───╯ ╭───╯ ╭───╯                              │
│    └──────────────────────────────────────►                  │
│                    停顿时间                                   │
│                    ◄── ───────►                               │
│                     短              长                       │
│                                                              │
└─────────────────────────────────────────────────────────────┘

配置与使用

ZGC 配置

bash
# JDK 11+ 启用 ZGC
java -XX:+UseZGC your.Application

# JDK 15+ 分代 ZGC(推荐)
java -XX:+UseZGC -XX:+ZGenerational your.Application

# 配置参数
-XX:MaxGCPauseMillis=1     # 停顿时间目标(软目标)
-XX:ConcGCThreads=4         # 并发 GC 线程数
-Xmx32g -Xms32g            # ZGC 支持大堆

# JDK 21+ 推荐配置
java -XX:+UseZGC \
     -XX:+ZGenerational \
     -XX:MaxGCPauseMillis=1 \
     -Xmx64g -Xms64g \
     your.Application

Shenandoah 配置

bash
# JDK 12+ 启用 Shenandoah
java -XX:+UseShenandoahGC your.Application

# 配置参数
-XX:ShenandoahGCHeuristics=adaptive   # 自适应策略
-XX:ConcGCThreads=4                    # 并发 GC 线程数
-XX:MaxGCPauseMillis=1                 # 停顿时间目标

适用场景

ZGC / Shenandoah 的最佳场景

场景原因
低延迟敏感应用金融交易、实时系统、游戏服务器
大堆应用支持 16TB+ 堆,GC 停顿仍 < 1ms
容器环境内存资源动态变化,ZGC 适应性好
JDK 11+ 新项目开箱即用的低延迟方案

不适合的场景

场景原因
小堆应用ZGC 的开销(读屏障、染色指针)在小堆下不划算
极高吞吐量需求ZGC 吞吐量比 Parallel 低约 10-15%
JDK 8 项目需要升级 JDK

面试追问方向

  • ZGC 的染色指针(Colored Pointers)是什么?为什么需要它?
  • ZGC 和 Shenandoah 都声称 < 1ms 停顿,它们是怎么做到的?
  • ZGC 的「自愈」机制是什么?为什么需要读屏障?
  • ZGC 为什么支持 16TB 堆?而其他收集器支持不了这么大?
  • ZGC 和 G1 在设计理念上有什么本质区别?
  • JDK 15 的分代 ZGC(ZGenerational)和原来的 ZGC 有什么区别?
  • ZGC 的吞吐量为什么比其他收集器低?牺牲了什么换取了低延迟?

基于 VitePress 构建