GC 算法与垃圾收集器演进:从 Serial 到 ZGC

GC 算法与垃圾收集器演进:从 Serial 到 ZGC

垃圾收集是 Java 面试的核心战场。从最基础的标记清除到 ZGC 的染色指针,本文把 GC 的演进脉络一次讲透。

一、GC 基础:如何判断对象可回收?

1. 引用计数法(已淘汰)

java 复制代码
// 引用计数法: 每个对象有一个引用计数器
// 但无法解决循环引用
Object a = new Object(); // a.count = 1
Object b = new Object(); // b.count = 1
a.ref = b;               // b.count = 2
b.ref = a;               // a.count = 2
a = null;                // a.count = 1
b = null;                // b.count = 1
// a 和 b 互相引用,count 永远 > 0 → 永远不会被回收!

2. 可达性分析(JVM 使用)

java 复制代码
// 从 GC Roots 出发,沿引用链遍历
// 不可达的对象 = 可回收对象

// GC Roots 包括:
// 1. 虚拟机栈中的引用 (局部变量)
// 2. 方法区中静态变量的引用
// 3. 方法区中常量的引用
// 4. 本地方法栈 JNI 引用
// 5. Java 虚拟机内部引用 (基本类型 Class)
// 6. 同步锁持有的对象
makefile 复制代码
可达性分析:

  GC Roots
    ├── 栈引用 ──▶ objA ──▶ objB ──▶ objC  (可达,不回收)
    ├── 静态变量 ──▶ objD                      (可达)
    │
    └── objX ──✗ (无引用链,不可达 → 可回收)
        objY ──✗
        objZ ──▶ objW ──✗ (objZ 可达但 objW 不可达)

3. 四种引用类型

java 复制代码
// 强引用: 永不回收(GC Roots 可达)
Object strong = new Object();

// 软引用: 内存不足时回收
SoftReference<Object> soft = new SoftReference<>(new Object());

// 弱引用: 下次 GC 就回收
WeakReference<Object> weak = new WeakReference<>(new Object());

// 虚引用: 不影响生命周期,只做跟踪通知
PhantomReference<Object> phantom = new PhantomReference<>(new Object(), new ReferenceQueue<>());
引用类型 回收时机 用途
强引用 永不(除非置 null) 普通对象
软引用 内存不足时 缓存
弱引用 下次 GC ThreadLocalMap、WeakHashMap
虚引用 随时 跟踪对象被回收的时机

二、GC 算法

1. 标记-清除(Mark-Sweep)

less 复制代码
标记-清除算法:

  标记阶段:                     清除阶段:
  ┌───┬───┬───┬───┬───┐      ┌───┬───┬───┬───┬───┐
  │ A │ ✗ │ B │ ✗ │ C │      │ A │   │ B │   │ C │
  └───┴───┴───┴───┴───┘      └───┴───┴───┴───┴───┘
       (✗ = 标记为可回收)        (清除后产生碎片)
  • 优点:实现简单
  • 缺点:产生内存碎片

2. 复制算法(Copying)

less 复制代码
复制算法:

  From 区:                      To 区:
  ┌───┬───┬───┬───┬───┐      ┌───┬───┬───┬───┬───┐
  │ A │ ✗ │ B │ ✗ │ C │  →   │ A │ B │ C │   │   │
  └───┴───┴───┴───┴───┘      └───┴───┴───┴───┴───┘
  (存活对象复制到 To 区)        (存活对象连续排列,无碎片)
  From 区整体清空
  • 优点:无碎片,分配快
  • 缺点:浪费一半空间

3. 标记-整理(Mark-Compact)

less 复制代码
标记-整理算法:

  标记阶段:                     整理阶段:
  ┌───┬───┬───┬───┬───┬───┐   ┌───┬───┬───┬───┬───┬───┐
  │ A │ ✗ │ B │ ✗ │ C │ ✗ │   │ A │ B │ C │   │   │   │
  └───┴───┴───┴───┴───┴───┘   └───┴───┴───┴───┴───┴───┘
  (标记可回收对象)               (存活对象向一端移动,清理边界外)
  • 优点:无碎片,不浪费空间
  • 缺点:移动对象开销大,STW 时间长

三种算法对比

算法 碎片 空间利用率 效率 适用区域
标记-清除 有 100% 中等 老年代(CMS)
复制 无 50% 最高 新生代
标记-整理 无 100% 较低 老年代

4. 分代收集算法

makefile 复制代码
分代收集策略:

新生代: 对象朝生夕死 → 复制算法
  ┌─────────────────────────────────┐
  │  Eden  │  S0  │  S1  │           │
  │  80%   │ 10%  │ 10%  │           │
  └─────────────────────────────────┘
  Minor GC: Eden + 活跃 Survivor → 空闲 Survivor
  每次存活 → 年龄 +1, 达到阈值晋升老年代

老年代: 对象存活率高 → 标记-清除/标记-整理
  ┌─────────────────────────────────┐
  │           Old Gen               │
  │  Major GC / Full GC             │
  │  标记存活对象 → 清除/整理         │
  └─────────────────────────────────┘

三、垃圾收集器演进

objectivec 复制代码
垃圾收集器演进时间线:

JDK 1.3  ──── Serial / Serial Old
JDK 1.4  ──── Parallel Scavenge / Parallel Old (吞吐量优先)
JDK 1.5  ──── CMS (低延迟, 标记-清除)
JDK 1.7u4 ──── G1 (可预测停顿)
JDK 11   ──── ZGC (染色指针, <10ms)
JDK 12   ──── Shenandoah ( Brooks 指针, <10ms)
JDK 15   ──── ZGC 正式可用, CMS 废弃
JDK 17   ──── ZGC Generational
JDK 21   ──── Generational ZGC (分代 ZGC)

性能对比:
  ┌────────────────────────────────────────────────┐
  │  停顿时间(ms)                                    │
  │                                                │
  │  Serial     ████████████████████████  500+     │
  │  Parallel   ████████████████          200     │
  │  CMS        ██████████               100     │
  │  G1         █████                      50     │
  │  ZGC        █                          <10    │
  │  Shenandoah █                          <10    │
  └────────────────────────────────────────────────┘

1. Serial / Serial Old

arduino 复制代码
  单线程收集器,STW 时只使用一个线程
  适用于: 客户端模式、小堆内存
  
  新生代(Serial):  复制算法
  老年代(Serial Old): 标记-整理
  
  -XX:+UseSerialGC

2. Parallel Scavenge / Parallel Old

ini 复制代码
  多线程收集器,关注吞吐量
  适用于: 后台计算、批处理
  
  新生代(Parallel Scavenge):  复制算法, 多线程
  老年代(Parallel Old):       标记-整理, 多线程
  
  -XX:+UseParallelGC
  -XX:MaxGCPauseMillis=200  (最大停顿时间)
  -XX:GCTimeRatio=99       (GC时间占比 1/(1+99)=1%)

3. CMS(Concurrent Mark Sweep)

markdown 复制代码
  低延迟收集器,关注停顿时间
  适用于: 用户交互场景
  
  四阶段:
  ┌─────────────────────────────────────────────┐
  │  1. 初始标记 (STW)                            │
  │     标记 GC Roots 直接关联的对象               │
  │     ──────── (极短)                          │
  │                                             │
  │  2. 并发标记 (并发)                           │
  │     沿引用链遍历, 标记所有可达对象              │
  │     ──────────────────────── (较长时间)      │
  │                                             │
  │  3. 重新标记 (STW)                            │
  │     修正并发标记期间引用变化的对象              │
  │     ────── (较短)                            │
  │                                             │
  │  4. 并发清除 (并发)                           │
  │     清除未标记的对象                           │
  │     ──────────────────────── (较长时间)      │
  └─────────────────────────────────────────────┘
  
  STW: 初始标记 + 重新标记 (很短)
  并发: 并发标记 + 并发清除 (大部分时间)
  
  缺点:
  - 标记-清除 → 内存碎片
  - 浮动垃圾 → 并发清除时新产生的垃圾
  - Concurrent Mode Failure → 退化为 Serial Old
  - 对 CPU 敏感 → 占用用户线程

4. G1(Garbage First)

ini 复制代码
  G1 将堆分为多个 Region, 不再是物理分代
  
  ┌──────────────────────────────────────────┐
  │              G1 堆内存布局                  │
  │                                          │
  │  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐         │
  │  │E │E │S │O │O │H │H │O │E │O │         │
  │  ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤         │
  │  │O │E │O │S │O │E │H │O │O │S │         │
  │  ├──┼──┼──┼──┼──┼──┼──┼──┼──┼──┤         │
  │  │E │O │O │E │S │O │H │O │E │O │         │
  │  └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘         │
  │  E=Eden S=Survivor O=Old H=Humongous      │
  │                                          │
  │  Region 大小: 1-32MB (2的幂)               │
  │  逻辑分代, 物理不连续                        │
  │  优先回收垃圾最多的 Region (Garbage First)  │
  └──────────────────────────────────────────┘
java 复制代码
// G1 四阶段
// 1. 初始标记 (STW, 搭载一次 Minor GC)
// 2. 并发标记 (并发, 跟踪 Region 中的对象)
// 3. 最终标记 (STW, 处理 SATB 记录)
// 4. 筛选回收 (STW, 按收益排序, 回收高收益 Region)

// G1 参数
// -XX:+UseG1GC
// -XX:MaxGCPauseMillis=200  // 目标停顿时间
// -XX:G1HeapRegionSize=16m  // Region 大小
// -XX:InitiatingHeapOccupancyPercent=45 // 触发并发标记的堆占用率
yaml 复制代码
G1 vs CMS:

  CMS:  整个老年代一起回收, 不可控
  G1:   按 Region 回收, 可选停顿时间

  CMS:  标记-清除 (碎片)
  G1:   复制+标记-整理 (Region间复制, 无碎片)

  CMS:  停顿时间不可预测
  G1:   -XX:MaxGCPauseMillis 可预测

5. ZGC(Z Garbage Collector)

yaml 复制代码
  ZGC: 染色指针 + 读屏障, 停顿 < 10ms

  核心技术:
  ┌────────────────────────────────────────┐
  │  1. 染色指针 (Colored Pointers)          │
  │     64位指针中用4位存储GC信息             │
  │     ┌────────────────────────────────┐ │
  │     │ 18位 unused │ 1 │ Finalizable   │ │
  │     │             │ 1 │ Remapped      │ │
  │     │             │ 1 │ Marked0       │ │
  │     │             │ 1 │ Marked1       │ │
  │     │ 42位地址空间                      │ │
  │     └────────────────────────────────┘ │
  │                                        │
  │  2. 读屏障 (Load Barrier)               │
  │     读取对象引用时自动检查颜色           │
  │     如果颜色不对 → 修正指针              │
  │     对象转移和重定位对应用透明           │
  │                                        │
  │  3. 并发转移                             │
  │     标记和转移都在并发执行               │
  │     STW 只有三个阶段, 每个 < 1ms         │
  └────────────────────────────────────────┘
css 复制代码
ZGC 工作流程:

  ┌─────────────┐   STW(<1ms)   ┌─────────────┐
  │  初始标记     │─────────────▶│  并发标记     │  并发
  └─────────────┘              └──────┬──────┘
                                       │
           ┌───────────────────────────┘
           ▼
  ┌─────────────┐   STW(<1ms)   ┌─────────────┐
  │  再定位根集   │─────────────▶│  并发转移     │  并发
  └─────────────┘              └──────┬──────┘
                                       │
           ┌───────────────────────────┘
           ▼
  ┌─────────────┐   STW(<1ms)   ┌─────────────┐
  │  再定位集     │─────────────▶│  并发重映射   │  并发
  └─────────────┘              └─────────────┘

  总停顿: 3次 STW, 每次 < 1ms
  可处理 TB 级堆内存

6. Shenandoah

yaml 复制代码
  Shenandoah: Brooks 指针 + 并发整理

  与 ZGC 区别:
  - ZGC: 染色指针 (64位指针存GC信息)
  - Shenandoah: Brooks 转发指针 (每个对象额外一个指针)

  对象内存布局:
  ┌───────────────────┐
  │ Brooks Pointer    │ ──▶ 新地址 (对象移动后)
  ├───────────────────┤
  │ 对象头             │
  ├───────────────────┤
  │ 实例数据           │
  └───────────────────┘

四、收集器对比总表

收集器 模式 算法 STW 目标 适用堆
Serial 单线程 复制 长 简单 < 100MB
Parallel 多线程 复制+整理 中 吞吐量 < 4GB
CMS 并发 标记-清除 短(有碎片) 低延迟 < 8GB
G1 并发 复制+整理 可控 可预测停顿 4-32GB
ZGC 并发 染色指针 <10ms 超低延迟 8GB-16TB
Shenandoah 并发 Brooks指针 <10ms 超低延迟 4GB-32GB
yaml 复制代码
选型指南:

  堆 < 100MB:        Serial
  堆 < 4GB, 关注吞吐量: Parallel
  堆 < 8GB, 关注延迟:  CMS (JDK 14已废弃) → G1
  堆 4-32GB:          G1 (JDK 9+ 默认)
  堆 > 8GB, 低延迟:    ZGC (JDK 15+)

五、常见面试追问

Q1:为什么 CMS 使用标记-清除而不是标记-整理?

CMS 追求低延迟,标记-整理需要移动对象,移动期间需要 STW(或更新所有引用),无法并发执行。标记-清除不需要移动对象,可以并发清除。代价是产生碎片。

Q2:G1 的 Mixed GC 是什么?

G1 的 Mixed GC 会回收新生代和部分老年代 Region。G1 优先回收垃圾最多的 Region(Garbage First),所以可以选择性地回收老年代中垃圾最多的 Region,而不是整个老年代。

Q3:ZGC 如何做到 < 10ms 停顿?

  1. 染色指针:指针中存储 GC 状态,无需遍历对象
  2. 读屏障:读取引用时自动修复指针,转移可并发
  3. 并发转移:标记和转移都在并发执行,STW 只有初始标记阶段

Q4:Full GC 和 Major GC 的区别?

  • Minor GC: 回收新生代(频繁,快)
  • Major GC: 回收老年代(通常伴随 Full GC)
  • Full GC: 回收整个堆 + 元空间(慢,应尽量避免)

Q5:什么是安全点(Safepoint)?

Safepoint 是程序执行中的"安全暂停点",JVM 在这些点可以安全执行 GC。Safepoint 通常在方法调用、循环回边等位置。JIT 编译器会在这些位置插入 safepoint 轮询指令。

六、总结

css 复制代码
GC 演进核心: 减少 STW 时间

标记清除 → 复制 → 标记整理 → 分代收集
                                     ↓
Serial → Parallel → CMS(并发) → G1(Region化) → ZGC(染色指针)

算法:  新生代用复制(对象朝生夕死)
       老年代用标记-整理(对象存活率高)
收集器: 吞吐量 → Parallel, 延迟 → G1/ZGC

GC 的演进方向始终是 减少停顿时间 和 支持更大堆内存,从 Serial 的数百毫秒到 ZGC 的亚毫秒级,Java 在低延迟方向上不断突破。

相关推荐
天天被压力2 小时前
【别再到处找免费股票数据API了:官方204个接口,32篇一次讲透 #02】涨停跌停与特色股池:5类股池接口一次拉全
java·人工智能·python
斯维赤2 小时前
从 @Tool 到 Agent 流水线:LangChain4j 进阶教学,一个库打全套
java·后端
SimonKing2 小时前
SSE项目`nexus-sse`持续优化,不一样的视觉效果
java·后端·程序员
斑鸠喳喳2 小时前
读写锁模式 Read-Write Lock
java·后端
信誓旦旦的程序猿2 小时前
【Python 量化取数指南 #09】Python 拿港股通数据:港股通成交与港股财报实测
java·python·股票数据api·股票数据·股票数据api接口·股票api数据接口·股票量化数据api
小程序设计3 小时前
本科毕设避坑|大数据 / 单片机物联网 / 网络工程 / PLC / 机械设计选题与落地心得
java·hive·hadoop·单片机
卓怡学长3 小时前
w206基于SpringBoot的社区团购小程序设计与实现
java·spring boot·spring·intellij-idea
IT枫斗者枫哥8 小时前
MyBatis一对多分页:LIMIT 20,为什么凑不齐20个订单?
java·数据库
代码方舟8 小时前
Java数据工程:利用天远全网运营商三要素优化线上实名认证合规体验
java·人工智能