# 垃圾收集器详解

作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)


如果说垃圾收集算法是内存回收的“方法论”,那么**垃圾收集器(GC Collector)**就是它的“具体实现”。

img


# 一、主流垃圾收集器总览

JVM 内置了多种收集器,它们各自适用于不同的应用场景与性能目标。 总体可分为三类:

分类 特点 代表收集器 目标
串行(Serial) 单线程,Stop-The-World,简单高效 Serial、Serial Old 小堆 / 单核环境
并行(Parallel) 多线程,关注吞吐量 Parallel Scavenge、Parallel Old 吞吐量优先
并发(Concurrent) 与应用线程并发执行,低停顿 CMS、G1 响应时间优先

# 二、各收集器详解

# Serial 收集器(新生代)

单线程、Stop-The-World 收集器,适用于小堆和单核环境。 GC 期间仅一个线程负责回收,所有用户线程暂停。

算法:复制算法(Copying) 将存活对象从 Eden、Survivor 区复制到另一块空闲 Survivor 区,避免碎片化。

执行流程:

  1. 新生代内存达到阈值触发 Minor GC;
  2. 暂停所有用户线程(STW);
  3. 单线程复制存活对象;
  4. 回收完成,恢复线程执行。

简单稳定,小内存下性能出色;但在多核或大堆场景下停顿明显。


# Serial Old 收集器(老年代)

Serial 的老年代版本,同样是单线程 STW。

算法:标记-整理(Mark-Compact) 先标记存活对象,再整理内存,保证空间连续性。

执行流程:

  1. 触发 Full GC → STW;
  2. 标记老年代存活对象;
  3. 整理、压缩内存;
  4. 恢复应用线程。

常用于单线程或 CMS 失败后的回退(Fallback)。


# ParNew 收集器(新生代)

Serial 的多线程版本,在多核 CPU 环境下提升 GC 效率。 属于 STW 并行收集器

算法:复制算法(Copying) 同 Serial,使用多线程并行复制对象。

执行流程:

  1. 触发 Minor GC → STW;
  2. 多线程并行复制存活对象到另一 Survivor 区;
  3. 完成后恢复应用线程。

JDK 8 以前常与 CMS 搭配使用:ParNew + CMS


# Parallel Scavenge 收集器(新生代)

与 ParNew 类似,但目标不同: 关注吞吐量(Throughput),即最大化程序运行时间、最小化 GC 时间。

算法:复制算法(Copying)

特性:

  • 自动调整堆比例与 GC 时间;
  • 通过参数 -XX:MaxGCPauseMillis-XX:GCTimeRatio 控制平衡点;
  • 适合批处理、后台计算等吞吐量优先场景。

流程:

  1. Minor GC → STW;
  2. 多线程复制存活对象;
  3. 根据吞吐量目标自动调整参数;
  4. 恢复应用线程。

# Parallel Old 收集器(老年代)

Parallel Scavenge 的老年代版本,同样注重高吞吐量

算法:标记-整理(Mark-Compact) 通过多线程并行标记和整理对象,避免碎片。

执行流程:

  1. Full GC → STW;
  2. 多线程并行标记存活对象;
  3. 多线程整理、压缩;
  4. 恢复线程。

Parallel Scavenge + Parallel Old吞吐量优先型组合,常用于计算密集型系统。


# CMS 收集器

JDK 14 起被移除,JDK 9 开始默认被 G1 替代。

CMS 是一款基于 标记-清除算法(Mark-Sweep) 的老年代收集器。 其最大特点是:在大部分回收阶段,GC 线程与用户线程并发运行,从而显著减少了 Stop-The-World 时间。

但是,由于不进行“压缩(Compact)”,会导致:

  • 内存碎片;
  • 晋升失败(Promotion Failed);
  • 甚至触发 Concurrent Mode Failure(回退到 Serial Old)。

执行阶段详解

阶段 是否 STW 描述
Initial Mark 快速标记与 GC Roots 直接可达的对象。时间极短。
Concurrent Mark 与用户线程并发,从 GC Roots 出发递归标记可达对象(使用三色标记法)。
Remark 再次暂停用户线程,补标 Concurrent Mark 阶段遗漏的引用变更(通过写后屏障 + 增量更新机制)。
Concurrent Sweep 与应用线程并发清理未标记对象(直接释放内存,不整理)。

关键技术点

  • 三色标记法(Tri-color Marking)

    • 使用白、灰、黑三种颜色追踪对象可达性;

    • 保证“黑对象不会指向白对象”,防止漏标;

    • CMS 使用 写后屏障 + 增量更新(Incremental Update) 来维持三色不变式。

  • 增量更新机制(Incremental Update)

    在应用线程修改引用时,CMS 会将新引用的目标对象标记为灰色, 避免出现“黑指白”的漏标问题。

    if (A is black && A.field = B)
        mark(B as gray);
    
    1
    2

优缺点总结

优点:

  • 并发执行,用户线程停顿极短;
  • 响应快,适合低延迟场景(如交易系统、实时服务)。

缺点:

  • 不整理内存,易产生碎片;
  • 并发阶段占用 CPU;
  • 若清理速度慢于分配速度,会触发 Concurrent Mode Failure
  • 维护成本高,JDK 14 已正式移除。

# G1 收集器

JDK 9 起默认收集器,面向大内存、低延迟场景。

设计目标:可预测的低停顿(-XX:MaxGCPauseMillis

基本原理

G1(Garbage-First)是一种面向服务端大堆(GB 级)的收集器,它在思想上是 CMS 的继任者,但从实现上是全新的体系结构:

  • 使用 Region 化堆结构
  • 通过 SATB 写前屏障 实现并发标记;
  • 通过 RSet 实现跨代引用追踪;
  • 通过 CSet 精确选择最优回收区域;
  • 支持 Evacuation(疏散复制),天然避免内存碎片。

堆结构:Region 化分区

整个堆被分成大量等大小的 Region(1~32MB)

Region 类型 说明
Eden Region 新生对象的分配区
Survivor Region 幸存对象的过渡区
Old Region 长期存活对象
Humongous Region 存放大对象(> 1/2 Region)

年轻代与老年代仍存在,但由 若干个 Region 动态组成,不是固定内存块。

核心机制详解

机制 作用
RSet(Remembered Set) 每个 Region 记录外部引用来源,避免全堆扫描
CSet(Collection Set) 每次 GC 挑选“垃圾比例高”的 Region 优先回收
SATB(Snapshot-At-The-Beginning) 写前屏障机制,确保并发标记期间不漏标

主要阶段流程

阶段 是否 STW 说明
Young GC(STW) 回收 Eden Region,将活对象疏散到 Survivor / Old。
Concurrent Mark 🚫 全堆并发标记(基于三色标记 + SATB 写前屏障)。
Remark(STW) 补标阶段,清理 SATB buffer,保证完整性。
Cleanup / Mixed GC ✅ + 🚫 统计各 Region 存活率;选择“垃圾多”的 Old Region 一起混合回收。
Evacuation(疏散复制) 复制活对象到新的 Region,实现天然压缩。

优点与特点总结

优势:

  • 自动控制暂停时间;
  • 不产生内存碎片;
  • 支持多核并发标记;
  • 分区粒度精细,可精确控制回收开销;
  • 适合高并发、大堆、低延迟场景。

不足:

  • 实现复杂;
  • 元数据维护(RSet)占用额外内存;
  • 在堆较小或低核环境下性能不如 Parallel。

# 三、垃圾收集器分类总结

分类 并行性 是否并发 主要算法 典型场景
Serial / Serial Old 单线程 复制 / 标记整理 小堆、单核
ParNew / CMS 多线程 + 并发 复制 / 标记清除 低延迟
Parallel Scavenge / Parallel Old 多线程 复制 / 标记整理 吞吐量优先
G1 并行 + 并发 标记-整理(Region化) 大堆、低延迟