这是「一文拿下 GC」系列第四篇。前面讲了「怎么判断该死 + 怎么回收」——这一篇讲这些理论在生产中的实际载体:HotSpot 提供的七大垃圾收集器。
一、HotSpot 垃圾收集器架构

架构说明:
- Young Generation(新生代):Serial、ParNew、Parallel Scavenge
- Tenured Generation(老年代):CMS、Serial Old (MSC)、Parallel Old
- G1:横跨新生代和老年代,独立于传统的分代模型
收集器搭配关系(图中箭头表示可用搭配):
- Serial → Serial Old
- ParNew → CMS、Serial Old
- Parallel Scavenge → Serial Old、Parallel Old
2017 年冷知识:当年 G1 刚成为 HotSpot 长期演进方向,但生产环境真正普及要到 Java 9(2017 年 9 月发布)成为默认收集器之后。Java 8(2014 年发布)默认还是 Parallel GC。
二、新生代收集器
2.1 Serial 收集器
JDK 1.3.1 之前虚拟机新生代收集器唯一选择——单线程的收集器。
「单线程」的意义:不是说它只会使用一个 CPU 或者一条垃圾回收线程完成垃圾收集,而是它进行垃圾收集时必须暂停其他所有工作线程,直到它收集结束(Stop The World)。
优势:
- 简单而高效(与其他收集器的单线程比)
- 没有线程交互的开销
- 有很高的单线程收集效率
- 适合运行在 Client 模式下(比如用户桌面应用场景)
- 收集几十兆一两百兆的新生代,停顿时间控制在几十毫秒一百毫秒以内,只要不频繁发生,这点停顿完全可以接受
开启参数:
-XX:+UseSerialGC -XX:+UseSerialOldGC
# 开启前面参数后,SerialOld 参数默认开启
2.2 ParNew 收集器
Serial 收集器的多线程版本。
- 默认开启收集线程数为 CPU 的数量
- 可通过
-XX:ParallelGCThreads参数限制垃圾收集器线程数 - 除了可多线程收集垃圾外,其余特性和 Serial 完全一样(同样 Stop The World)
特殊地位:
- ParNew 是目前能和 CMS 组合使用的除 Serial 外的唯一可用的新生代收集器
- 也是使用
-XX:+UseConcMarkSweepGC选项后默认的新生代收集器 - 也可使用
-XX:+UseParNewGC强行指定它
2.3 Parallel Scavenge 收集器
同 ParNew 一样是并行的多线程收集器,但目标不同:
核心目标:达到一个可控制的吞吐量(Throughput)。
吞吐量的定义:CPU 运行用户代码的时间与 CPU 总消耗的时间的比值——如虚拟机执行了 100 分钟,其中 1 分钟是垃圾回收花掉的,那么吞吐量为 99%。
停顿时间就是 GC 时其他工作线程等待的时间。停顿时间越短越适合需要与用户交互的程序。
关键参数:
-XX:MaxGCPauseMillis=<ms>:最大停顿时间(优先满足)-XX:GCTimeRatio=<n>:吞吐量(1/(1+n))
重要警告:
并不是将停顿时间设置的越小越好——停顿时间是牺牲吞吐量和新生代空间换来的。吞吐量小,只能设置更小的新生代内存,导致垃圾收集发生的更频繁,降低吞吐量(自适应)。
自适应特性:根据配置参数自动调整堆的大小
开启参数:
-XX:+UseParallelGC -XX:+UseParallelOldGC
-XX:ParallelGCThreads=n # 开启多少个垃圾回收线程
ParallelGCThreads 默认值:CPU 核心大于 8 时,开启 5/8 个;否则为 CPU 核心数。
使用场景:
- Server 模式下默认的收集器
- 吞吐量优先的场景(后台计算、离线任务、不太需要快速响应的服务)
三、老年代收集器
3.1 Serial Old 收集器
Serial 的老年代收集器——单线程收集器,使用标记-整理算法。
两大用途:
- JDK 1.5 及以前版本与 Parallel Scavenge 收集器搭配使用
- 作为 CMS 预备方案——在 CMS 发生 Concurrent Mode Failure 时使用(CMS 的失败保护机制)
3.2 Parallel Old 收集器
使用标记-整理算法,JDK 1.6 开始提供。
- 与 Parallel Scavenge 搭配使用
- 注重吞吐量和 CPU 敏感场合都可优先使用
2017 年冷知识:JDK 1.6 引入 Parallel Old 是Parallel Scavenge 终于有了「般配」的老年代搭档——之前的版本里 Parallel Scavenge 只能跟 Serial Old 搭配,浪费了多核能力。
3.3 CMS 收集器
以获取最短回收停顿时间为目标的收集器——适合在 B/S 架构服务器端使用,基于标记-清除算法。
3.3.1 运作过程:四阶段
- 初始标记(CMS initial mark)— STW
- 标记 GC Roots 能直接关联到的对象
- 速度很快
- 并发标记(CMS concurrent mark)
- 进行 GC Roots Tracing 过程
- 与用户线程并发执行
- 重新标记(CMS remark)— STW
- 修正并发标记期间因应用程序继续运作而导致标记产生变动的那一部分对象的标记记录
- 停顿时间稍微比初始标记长,但远比并发标记短
- 并发清除(CMS concurrent sweep)
- 与用户线程并发执行
3.3.2 缺点
1. 对 CPU 资源非常敏感
其实面向并发设计的程序都对 CPU 资源比较敏感。并发阶段虽不会导致用户线程停顿,但会因占用 CPU 资源导致应用程序变慢,总吞吐量下降。
CMS 默认启动回收线程数 = (CPU 数 + 3) / 4——当 CPU 数不足 4 个时,对用户程序影响很大。比如双核,要分出去一半去收集垃圾,导致用户程序执行效率突然降低 50%。
2. 无法处理浮动垃圾
CMS 并发清理阶段用户程序也在运行,自然有新垃圾产生——如果垃圾出现在标记之后,本次垃圾收集就不能及时清理,这就是浮动垃圾。
3. Concurrent Mode Failure
由于垃圾收集阶段需要和用户线程并行,因此不能像其他垃圾收集器那样几乎老年代完全填满再进行收集——需要预留部分空间提供并发收集时程序运行使用。
可使用 -XX:CMSInitiatingOccupancyFraction 值修改老年代空间占用百分比(JDK 1.6 开启,此值为 92%)。
要是 CMS 运行期间预留内存无法满足程序需求,就会发生 Concurrent Mode Failure——此时启动后备预案:使用 Serial Old 重新收集老年代,这样停顿时间就可能很长。
4. 空间碎片
CMS 是基于标记-清除算法的,垃圾收集完成后会产生大量空间碎片。这样在分配大对象时,可能出现明明老年代剩余空间很大但是找不到足够的连续空间分配大对象不得不提前触发一次 FullGC。
解决方案:
-XX:+UseCMSCompactAtFullCollection(默认开启):用于 FullGC 时开启内存碎片的合并整理过程- 内存碎片整理无法并发,停顿时间需加长
-XX:CMSFullGCsBeforeCompaction:设置执行多少次不压缩的 FullGC 后跟着来一次带压缩的(默认值为 0,表示每次 FullGC 都要进行碎片整理)
3.4 G1 收集器
G1(Garbage-First)是 HotSpot 的「面向服务端应用的收集器」,目标是在停顿时间可控的前提下最大化吞吐量。
核心创新:
- 整体看是标记-整理算法(避免碎片)
- 局部(两个 region 之间)看是复制算法(高效)
- 可预测的停顿:用户可以指定期望的停顿时间
-XX:MaxGCPauseMillis - 不再坚持分代模型——把堆划分成多个大小相等的独立 region(默认 2048 个),每个 region 可以扮演 Eden/Survivor/Old 中的任何角色
2017 年冷知识:G1 引入的 region 模型是 GC 史上一次重大变革——它打破「堆必须按分代物理划分」的传统假设。后来(2018+)的 ZGC、Shenandoah 都借鉴了 region 思想。
四、七大收集器对比
| 收集器 | 代 | 算法 | 线程 | STW | 目标 | 适用场景 |
|---|---|---|---|---|---|---|
| Serial | Young | 复制 | 单线程 | 长 | 简单 | Client 桌面 |
| ParNew | Young | 复制 | 多线程 | 长 | 与 CMS 搭配 | 服务端 + CMS |
| Parallel Scavenge | Young | 复制 | 多线程 | 中 | 吞吐量优先 | 后台计算 |
| Serial Old | Old | 标记-整理 | 单线程 | 长 | 简单 | CMS 备用 |
| Parallel Old | Old | 标记-整理 | 多线程 | 中 | 吞吐量优先 | 后台计算 |
| CMS | Old | 标记-清除 | 并发+STW | 短 | 停顿最短 | B/S 架构服务端 |
| G1 | Both | 整体标记-整理+局部复制 | 并发+STW | 可控 | 停顿可控 | 通用(服务端) |
五、选择收集器
经验法则:
- 客户端 / 桌面应用 → Serial
- 服务端 + 吞吐量优先(后台计算)→ Parallel Scavenge + Parallel Old
- 服务端 + 停顿优先(Web API)→ ParNew + CMS(G1 普及前)
- 服务端 + 通用需求(Java 9+)→ G1
小结
- 三大新生代收集器:Serial(单)、ParNew(多)、Parallel Scavenge(吞吐)
- 三大老年代收集器:Serial Old(单)、Parallel Old(多)、CMS(停顿)
- G1 是 Java 9+ 的默认收集器,region 模型是 GC 演化方向
下一篇是GC 系列的最后一篇——堆内存区域全景,从 Eden/Survivor/Old 到 PermGen/Metaspace 的完整解析。