真实面经题目 · 原创解析

常见 JVM 垃圾收集器分别适合什么场景?

垃圾回收器适用场景不能只背名称,而要围绕吞吐量、停顿时间、堆大小、对象生命周期、CPU 余量和运行时版本来选择。Serial 适合小堆和低资源环境,Parallel 适合吞吐优先任务,CMS 是旧版本低停顿方案但有碎片和并发失败风险,G1 是较新主流服务端的平衡型选择,ZGC 与 Shenandoah 面向更严苛的低停顿和大堆场景,Epsilon 只适合测试、压测和短生命周期实验。

出现于:阿里巴巴 · 后端开发

60 秒回答模板

回答时可以先给分类框架:GC 选择本质是在吞吐量、延迟、内存占用和实现复杂度之间做权衡。Serial 是单线程 Stop-The-World,适合小堆、单核、客户端工具或极小容器;Parallel 是多线程 Stop-The-World,追求总体吞吐,适合批处理、离线计算、后台任务,能接受较长暂停;CMS 主要面向旧版本老年代低停顿,通过并发标记清除减少停顿,但不压缩,容易碎片化,可能出现 concurrent mode failure 后退化为长时间 Full GC;G1 把堆切成 Region,按收益优先回收,支持 Young GC 和 Mixed GC,用软性的停顿目标在吞吐和延迟之间折中,适合中大堆、服务端在线系统;ZGC 和 Shenandoah 把标记、转移、压缩等大量工作并发化,适合大堆、p99 延迟敏感、暂停预算很小的服务,但需要更多 CPU 和版本支持;Epsilon 不做回收,只分配到 OOM,适合性能基准、内存压力测试和短生命周期任务。最终选择应从 SLA 出发:先确定可接受最大停顿、吞吐目标、堆大小、分配速率、存活对象比例和 CPU 余量,再结合 GC 日志和压测数据验证,而不是只看默认参数。

考点 总体选择框架
主线 Serial 收集器
易错点 只说某个收集器更先进,却不说明吞吐、延迟、内存占用和 …

深入解析

01

总体选择框架

垃圾回收器的场景匹配首先看目标,而不是看哪个名字更新。吞吐优先意味着希望应用线程占用更多 CPU 时间,可以接受较长 Stop-The-World;延迟优先意味着关注单次暂停、p95、p99、p999 抖动,愿意用更多 CPU、内存或屏障开销换更短暂停;内存优先则要看元数据、Remembered Set、并发线程和预留空间成本。再结合堆大小、活跃对象比例、对象晋升速度、分配峰值、容器限制和 JDK 版本,才能做出可靠判断。

02

Serial 收集器

Serial 使用单线程完成年轻代和老年代回收,回收期间应用线程停止,年轻代通常采用复制算法,老年代采用标记整理。它的优势是实现简单、额外线程少、元数据和调度开销低,在单核、小堆、命令行工具、单元测试、嵌入式环境、资源很小的容器中反而可能更稳定。它不适合高并发在线服务,因为堆稍大或对象存活率升高时,单线程暂停会明显拉长,尾延迟很难控制。

03

Parallel 收集器

Parallel 也叫吞吐量优先收集器,年轻代 Parallel Scavenge 和老年代 Parallel Old 都以多线程 Stop-The-World 为核心。它适合批处理、报表计算、离线任务、定时作业、日志处理、科学计算等场景,因为这些任务更关心单位时间完成多少工作,而不是某一次暂停是否达到几十毫秒以内。它的典型问题是暂停时间可能较长,堆越大、存活对象越多,Full GC 影响越明显,因此不适合作为严格低延迟服务的首选。

04

CMS 收集器

CMS 的目标是降低老年代停顿,核心流程包括初始标记、并发标记、重新标记和并发清除,其中初始标记与重新标记仍然需要 Stop-The-World。它适合早期 JDK 中对老年代暂停敏感、又无法使用更新收集器的在线服务。缺点也很典型:清除后不整理,容易产生内存碎片;并发阶段会与应用线程竞争 CPU;并发清理期间新产生的浮动垃圾只能等下次处理;老年代空间预留不足时会触发 concurrent mode failure,退化为代价很高的 Full GC。

05

G1 收集器

G1 将整个堆拆成多个 Region,不再固定用连续的新生代和老年代空间,而是在 Region 上动态扮演 Eden、Survivor、Old 或 Humongous 角色。它会通过并发标记估算每个 Region 的垃圾收益,优先回收垃圾最多、收益最高的区域,并通过疏散复制实现局部整理。G1 支持 Young GC 和 Mixed GC,Mixed GC 会在回收年轻代的同时选择部分老年代 Region,因此适合中大堆、分配波动明显、希望通过软性停顿目标获得延迟和吞吐平衡的服务。

06

ZGC 收集器

ZGC 面向超低停顿,尽量把标记、重定位、引用处理等工作并发化,暂停时间通常不随堆大小线性增长。它适合大堆、缓存型服务、实时交互、交易链路、搜索推荐、网关和其他对 p99 延迟非常敏感的系统。代价是读屏障、并发线程和内存预留会带来额外成本,对 CPU 余量和 JDK 版本有要求。较新的运行时还在持续推进代际化能力,使它更贴近多数对象朝生夕死的分配规律,但具体行为应以实际运行时支持为准。

07

Shenandoah 收集器

Shenandoah 同样是低停顿收集器,特点是把压缩和对象移动也尽量并发执行,让暂停时间与堆大小的关联显著降低。它适合大堆、低延迟、老年代存活对象较多、希望减少碎片影响的服务,也适合对 Full GC 抖动特别敏感的场景。它的权衡点在于并发屏障和后台回收线程会消耗 CPU,吞吐可能不如 Parallel 或调优良好的 G1;不同 JDK 发行版的可用性和成熟度也要提前确认。

08

Epsilon 收集器

Epsilon 是 no-op 收集器,只负责内存分配,不做真正的垃圾回收,堆耗尽后直接 OOM。它不适合长期运行服务,也不能解决内存泄漏。它的价值在于特殊实验:隔离 GC 对基准测试的影响、测量纯分配性能、验证应用真实内存需求、做内存压力边界测试,或运行生命周期很短且确定在堆耗尽前结束的任务。能准确说明 Epsilon 的限制,通常比简单说它快更体现理解。

09

默认与验证

较新的主流 HotSpot 服务端运行时通常把 G1 作为通用平衡型默认选择,但默认不等于永远最佳,也不等于所有环境一致。旧版本、容器资源限制、发行版差异、显式 JVM 参数都可能改变实际收集器。严谨做法是查看启动日志、打印 JVM 参数、采集 GC 日志,再用真实流量或压测观察暂停分布、分配速率、晋升速率、老年代占用、Full GC 次数和 CPU 使用率,最后根据 SLA 决定是否从 G1 切换到 Parallel、ZGC 或 Shenandoah。

易错点

  • 只说某个收集器更先进,却不说明吞吐、延迟、内存占用和 CPU 成本之间的取舍。
  • 把 G1 的停顿目标理解成硬实时保证,忽略它只是尽量满足的软性目标。
  • 认为 CMS 没有长暂停风险,遗漏碎片化、浮动垃圾和 concurrent mode failure。
  • 把 ZGC 或 Shenandoah 当成所有场景的最优解,没有说明并发屏障和 CPU 开销。
  • 给出选择结论时没有结合 SLA、堆大小、分配速率、存活对象比例和压测数据。

面试官追问

如果一个服务要求吞吐量最高,应该优先考虑哪个收集器?

优先考虑 Parallel,因为它的目标就是提升吞吐量,通过多线程 Stop-The-World 尽快完成年轻代和老年代回收,让应用在整体周期内获得更高的有效运行时间。典型场景是批处理、离线计算、定时任务、报表生成等。这类任务通常不要求每次请求都在很短时间内返回,因此可以接受几百毫秒甚至更长的暂停。若是在线服务,还要看暂停是否会影响尾延迟。

CMS 为什么会出现 concurrent mode failure?

CMS 的老年代回收大部分与应用线程并发执行,但并发期间应用仍在分配和晋升对象。如果老年代剩余空间不足,或者清理速度跟不上对象晋升速度,就可能在 CMS 完成前没有足够空间接收新对象,此时会触发 concurrent mode failure。JVM 通常需要退回到更重的 Full GC 来整理和回收老年代,暂停时间会明显变长,这也是 CMS 在大堆和高分配压力下的主要风险。

G1 为什么比 CMS 更适合较新的通用服务端场景?

G1 不依赖连续老年代空间,而是把堆拆成 Region,并通过疏散复制实现局部整理,天然缓解 CMS 的碎片问题。它还会根据并发标记结果选择垃圾收益较高的 Region,配合 Young GC 和 Mixed GC,把回收工作拆成多个可控批次。对于中大堆在线服务,G1 往往能在吞吐和暂停之间取得更稳妥的平衡。不过 G1 的暂停目标是软目标,仍需通过 GC 日志验证。

ZGC 和 Shenandoah 有什么共同点和区别?

二者共同点是都面向低停顿,都会把大量 GC 工作并发化,目标是让暂停时间尽量不随堆大小线性增长,适合大堆和尾延迟敏感的服务。区别在于内部实现机制、屏障设计、对象移动方式、版本可用性和发行版支持存在差异。选择时不能只看名字,应检查当前运行时是否支持、是否启用代际能力、CPU 余量是否足够,并通过真实负载比较吞吐损失和暂停收益。

回答这类题时怎样体现不是死记硬背?

关键是把每个收集器放回目标和代价中解释:Serial 是低开销小堆,Parallel 是吞吐优先,CMS 是旧版本低停顿但有碎片和并发失败,G1 是 Region 化的平衡型方案,ZGC 和 Shenandoah 是大堆低停顿方案,Epsilon 是无回收实验工具。最后补一句选择流程:根据 SLA、堆大小、分配速率、对象存活率、CPU 余量和 GC 日志压测结果决定。