Serial、Parallel 与 CMS
Serial、Parallel 与 CMS 是分别侧重低开销、吞吐和短停顿的垃圾收集器。本文先比较它们怎样执行回收,再说明 CMS 为什么需要空闲空间,以及并发失败如何带来长停顿。CMS 以 JDK 8 的历史实现为背景,它已在 JDK 14 移除。
1. 面试核心
先记住这几条,再读正文- Serial。 Serial 使用单个 GC 线程完成主要回收工作,期间暂停应用。
- Parallel。 Parallel 使用多个 GC 线程并行回收,以吞吐为主要目标。卡 · 三者的比较
- CMS 停顿阶段。 JDK 8 CMS 的初始标记和重新标记会暂停应用。卡 · CMS
- 碎片与浮动垃圾。 CMS 正常并发周期采用标记清除,留下的空闲块会形成碎片,标记后才不可达的对象会成为浮动垃圾。
- 并发回收失败。 CMS 来不及回收或空闲块无法满足分配时,会发生 concurrent mode failure。卡 · 并发模式失败
2. Serial
Serial 用单个 GC 线程完成主要回收工作,期间暂停应用线程。这种暂停称为 Stop-The-World(STW),可用以下参数选择 Serial:
java -XX:+UseSerialGC -jar app.jar
单线程省去了多个 GC 线程之间的协调成本,所以适合小堆、单核或 CPU 配额很少的简单程序。这类程序也更容易受益于较低的收集器自身开销。
Serial 的年轻代采用复制,老年代采用标记整理,也就是移动存活对象以形成连续空闲空间。存活工作集指回收后仍然存活的对象集合。它越大,单线程需要处理的对象越多,应用等待回收结束的时间就越长。
3. Parallel
Parallel 让多个 GC 线程同时完成回收,以提高应用在总运行时间中的占比。吞吐优先的任务可以用以下参数选择它:
java -XX:+UseParallelGC -jar batch-job.jar
Parallel 的主要回收工作仍在 STW 中完成。多个 GC 线程分担工作,可以缩短一次回收实际经过的时间,但应用仍需集中暂停。批处理和离线计算更关注单位时间完成的任务量,因此更容易接受这种取舍。
这里的吞吐目标可表示为 应用时间 / (应用时间 + GC 时间)。增加 GC 线程的收益受以下资源限制影响:
- CPU 配额限制同时能运行多少线程,GC 还会与同机其他应用争抢 CPU。
- 扫描和复制对象需要内存带宽,带宽饱和后增加线程的收益会降低。
- NUMA 机器访问远端内存的成本更高。NUMA 指不同处理器访问各内存区域的速度不同。
- 多线程分配任务和合并结果需要协调,线程越多也会增加这部分开销。
-XX:MaxGCPauseMillis 设置期望的停顿时间,-XX:GCTimeRatio=N 设置 GC 时间占比目标 1/(N+1)。比如,N 为 99 时,目标是把 GC 时间控制在总时间的 1%。收集器会围绕目标调整,但实际能否达到仍取决于负载和可用资源。
4. CMS
CMS 的全称是 Concurrent Mark Sweep,即并发标记清除。JDK 8 的 CMS 主要回收老年代,年轻代配合 ParNew 回收。它把大部分标记和清除放在应用运行期间完成,以缩短老年代回收的暂停。
理解 CMS,需要区分以下四个主要阶段:
| 主要阶段 | 应用线程 | 工作 |
|---|---|---|
| 初始标记 | 暂停 | 标记从根和年轻代直接可达的老年代对象 |
| 并发标记 | 运行 | 遍历可达对象图 |
| 重新标记 | 暂停 | 处理并发期间的引用变化,完成标记 |
| 并发清除 | 运行 | 回收已判定不可达的老年代空间 |
应用在并发标记时仍会修改对象引用。重新标记需要补上这些变化,因此要再次暂停应用,完成存活对象的标记。实际日志还包含预清理和重置等阶段。
并发线程会占用 CPU,使业务能用于计算的资源减少。所以,CMS 用更多并发开销换取较短暂停,应用吞吐也会受到影响。
4.1 碎片与浮动垃圾
CMS 的碎片与浮动垃圾来自不同原因,需要分别判断。正常标记清除会回收不可达对象,但保留存活对象的位置,所以清除后会留下分散的空闲块。大对象需要合适大小的连续空间,即使总空闲量足够,也会因找不到合适块而分配失败。
浮动垃圾则是已标记为存活、之后才变成不可达的对象。本轮依据已有标记保留它们,下轮再识别并回收。因此,碎片影响空闲空间能否使用,浮动垃圾影响垃圾何时被回收。
4.2 并发模式失败
并发回收需要时间,CMS 必须在老年代空间耗尽前完成回收。应用在这段时间仍会分配对象,年轻代对象也会晋升,也就是进入老年代。
如果老年代先被占满,或空闲块无法满足分配,就会发生 concurrent mode failure,称为并发模式失败。CMS 随后暂停应用,转入停顿中的回收路径,因此会出现更长的暂停。
分析失败原因时,需要区分以下空间供给压力:
- 周期启动太晚,剩余空间不足以支撑整个回收周期。
- 分配或晋升过快,老年代空间比预计更早用完。
- 存活工作集过大,回收后能腾出的空间有限。
- 碎片严重,总空闲量足够却缺少合适大小的空闲块。
所以,日志中的老年代占用、空闲块分布和并发周期时长需要放在一起看。
5. CMS 的移除
CMS 在 JDK 9 弃用,在 JDK 14 移除。因此,Java 17、21 已无法用 -XX:+UseConcMarkSweepGC 选择它。
迁移旧服务时,应清理 CMS 和 ParNew 等旧收集器的专用参数。永久代参数、分代比例也要按目标 JDK 重新核对。可以先用 G1 的默认参数建立基线,观察堆占用、暂停分布和吞吐,再按目标调整或比较其他收集器。
G1 的参数与区域机制见 G1。
6. 三者的比较
三者的区别在于谁执行回收,以及应用是否同时运行。下表把收益和代价放在一起比较:
| 收集器 | 回收方式 | 主要收益 | 主要代价 |
|---|---|---|---|
| Serial | 单线程 STW | 协调成本低 | 大工作集停顿长 |
| Parallel | 多线程 STW | 吞吐 | 仍有集中停顿 |
| CMS | 老年代并发标记清除 | 缩短老年代停顿 | CPU、碎片和空间余量 |
并行描述多个 GC 线程同时工作。并发描述 GC 与应用同时运行。CMS 也保留 STW 阶段,两种概念可以出现在同一收集器中。
7. 收集器选择
选择收集器时,先把业务目标转换成可测量的指标。可以依次检查以下选择依据:
- 业务是批处理还是在线请求,主要追求吞吐还是低延迟。
- 可接受的 P99、P999 暂停和端到端请求延迟是多少。它们分别表示 99%、99.9% 的观测值低于该值。
- 堆大小、存活工作集和分配率是多少。分配率表示单位时间新分配的对象占用多少空间。
- 容器有多少 CPU 配额,堆外内存还剩多少余量。
- 目标 JDK 与发行版实际支持哪些收集器。
- 在同一负载下运行完整的稳定周期,对比吞吐、暂停分布、CPU 和进程内存。
默认收集器可以作为比较起点,是否合适仍需实测。GC 暂停只是请求端到端延迟的一部分,单次回收用时无法说明完整业务效果。
低延迟方案见 低延迟垃圾收集器。
8. 常见问题
读收集器名称和日志时,要把线程并行、应用暂停与空闲空间分开判断。
8.1 Parallel 的应用暂停
Parallel 的多个 GC 线程同时工作,但主要回收阶段仍暂停应用线程。这里的“并行”描述 GC 线程之间的关系;应用与 GC 同时运行才称为“并发”。
8.2 CMS 的连续空间需求
CMS 清除垃圾后保留存活对象的位置,空闲空间会分散在对象之间。大对象需要合适大小的连续空闲块,所以判断是否能分配时,还要看空闲块的大小分布。
8.3 旧系统的 CMS 知识
CMS 知识仍用于阅读 JDK 8 系统的 GC 日志,以及解释迁移中的暂停和容量变化。为 Java 17、21 配置新服务时,应选择目标版本支持的收集器,再建立性能基线。
9 · 面试题卡片
9.1 Serial、Parallel 和 CMS 的目标有什么区别
出现公司:阿里云、阿里巴巴
考察重点:区分并行和并发,说明三种收集器怎样在吞吐、停顿、CPU 与内存成本之间取舍。
相关内容:三者的比较。
参考回答
Serial 侧重低协调开销,Parallel 侧重吞吐,CMS 侧重缩短老年代回收的停顿。
Serial 在 STW 中用单线程回收,适合小堆和有限 CPU。Parallel 同样暂停应用,但用多个 GC 线程分担工作,以减少回收占用的总时间。CMS 则将老年代的大部分标记和清除与应用并发执行,代价是 CPU 竞争、额外空间余量和碎片。
CMS 已在 JDK 14 移除,迁移到 Java 17、21 时,需要为 G1 等目标收集器重新建立压测基线。
9.2 为什么 CMS 可能发生 concurrent mode failure
出现公司:阿里巴巴
考察重点:解释并发期间的分配和晋升怎样消耗老年代空间,以及启动时机、碎片与失败停顿的关系。
相关内容:并发模式失败。
参考回答
CMS 在并发周期结束前无法满足老年代分配,就会发生 concurrent mode failure。
应用在回收期间仍会分配对象,并把年轻代对象晋升到老年代。如果周期启动太晚、晋升太快或存活工作集过大,空闲空间就会提前耗尽。碎片也会让大对象找不到合适的空闲块。
这时 CMS 会暂停应用,完成回收,所以失败带来的停顿明显变长。排查需要同时比较老年代占用、分配和晋升速度,以及并发周期时长。
9.3 CMS 哪些阶段会暂停应用,碎片与浮动垃圾有何区别?
出现公司:暂无已核验的公开来源(本站自测)
考察重点:说明初始标记与重新标记为什么需要暂停,并区分碎片和浮动垃圾的成因及影响。
参考回答
JDK 8 CMS 的初始标记和重新标记会暂停应用,并发标记与清除则让应用继续运行。
初始标记确定直接可达的老年代对象,重新标记补上并发期间的引用变化。正常清除保留存活对象的位置,回收后的空洞形成碎片,影响大对象分配。浮动垃圾则在标记后才变为不可达,本轮仍被保留,留到后续周期回收。两者分别影响空间布局与回收时机。