题目:项目中如何选择垃圾回收器?为啥选择这个?
难度:high
标签:jvm/gc, general/knowledge, difficulty/high
评分:⭐⭐⭐⭐☆(4/5)
GM 垃圾回收器的选择,其实核心要点,首先是当然是业务情况,但是更很大的一个制约其实是当前业务使用的 GM 的版本。因为垃圾回收器是一个一直在迭代提升的过程。最初的垃圾回收器单线程,为了快,然后处理。然后后续的一点点资源越来越多了,然后单线程的垃圾回收器造成的 Stop the world 太长了,就考虑多线程,然后并发,一点点往下延。然后到 CMS 垃圾回收器,但是它本质上其实没发展起来。在 1.8 的时候,开始有 G1 的之后,开始大量的使用 G1。因为它本身的效率更高吧,它的选择的方式也相对来说对资源的利用更好。但是 G1 也有自己的局限性,当使用的服务的空间很大了之后,G1 也无法承担超过是 48G 还是 96G 的内存空间的管理。然后产生了 ZGC,然后对大的内存去进行管理。所以这垃圾回收器选择,一个是看你服务的情况,具体要用多少内存空间,看的版本,毕竟低版本是没有对应的高版本的新的垃圾回收器的。现在我们主要都是使用 G1 去进行配置垃圾回收器,去进行垃圾回收。原因主要在于配置相对简单,然后效率能力相对好。另外还有就是我们使用的 JVM 版本,我们 JVM 版本主要使用 JDK8,并没有使用最新的,没有往下使用当前的最新的 ZGC 去进行管控。另外我们服务的配置内存也没达到那么大的量级,我们服务的配置内存基本是在 16G 或者 32G 的服务器上,然后配置内存并没有那么高,基本上是 14G16G 的时候配了 14G 内存,配的没那么大。
优点:
不足:
-XX:MaxGCPauseMillis(G1 目标停顿)、-XX:InitiatingHeapOccupancyPercent(并发标记启动阈值)、-Xms -Xmx 等核心参数-Xlog:gc*、JFR、Prometheus + JVMExporter 等如何评估当前 GC 是否合适补充:
| GC 器 | 吞吐 | 停顿 | 空间 | 适合场景 |
|---|---|---|---|---|
| Serial | 低 | 长 | 高 | 小堆、单核(嵌入式) |
| ParallelGC(JDK 8 默认) | 高 | 中 | 中 | 批处理、后台任务 |
| CMS | 中 | 短(但有碎片) | 中 | 已弃用(JDK 14 移除) |
| G1(JDK 9+ 默认) | 中 | 可预测 | 中 | Web 服务、通用(推荐 4-32GB) |
| ZGC | 中 | 极短(<10ms) | 中 | 大堆(4GB+)、低延迟 |
| Shenandoah | 中 | 短(<10ms) | 中 | 大堆、并发优先 |
-XX:+UseG1GC:启用 G1-XX:MaxGCPauseMillis=200:目标停顿(默认 200ms)-XX:G1HeapRegionSize=16m:Region 大小(默认动态)-XX:InitiatingHeapOccupancyPercent=45:并发标记启动阈值(默认 45%)-Xms -Xmx:堆大小(建议相等避免动态扩缩)-XX:+UseZGC-Xlog:gc*(JDK 9+)或 -XX:+PrintGCDetailsjdk.GCPhasePause、jdk.GCHeapSummary核心考点定位:考察 GC 器选型的决策框架(业务场景 + JDK 版本 + 内存规模 + 权衡三角)+ 实战参数调优+ 评估手段。
知识点拆解:
-XX:MaxGCPauseMillis 目标停顿,G1 自动决定每次回收多少 Region-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=45-XX:+UseZGC -Xms -Xmx(建议相等)-Xms=-Xmx(避免动态扩缩开销)-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m-Xlog:gc*(JDK 9+)、-XX:+PrintGCDetails(JDK 8)jdk.GCPhasePause、jdk.GCHeapSummary、jdk.Safepoint| 场景 | 堆大小 | JDK 版本 | 推荐 GC |
|---|---|---|---|
| 嵌入式/单核 | <1GB | 任意 | Serial |
| 批处理/后台 | 4-32GB | JDK 8+ | ParallelGC |
| Web 服务通用 | 4-32GB | JDK 8+ | G1(推荐) |
| Web 服务低延迟 | 4-32GB | JDK 15+ | ZGC |
| 大堆服务 | 32GB-16TB | JDK 15+ | ZGC |
| 需要并发优先 | 32GB+ | JDK 11+ | Shenandoah |
考察方向:
延伸递进面试题:
-XX:G1HeapRegionSize 调 Region 大小-Xlog:gc*、JFR jdk.GCPhasePause、Prometheus + JVMExporter;关键指标:GC 频率、STW 时长 P99、FullGC 次数、堆使用率-Xlog 替代 -XX:+Print*面试官您好,GC 器选型本质上是在吞吐 vs 停顿 vs 空间的权衡三角里做取舍,实际选型我用三层漏斗决策。
第一层是 JDK 版本。低版本用不了新 GC 器——JDK 8 用不了 ZGC,JDK 14 之后 CMS 被移除。这是硬约束。
第二层是内存规模。Serial 适合小堆单核(<1GB),G1 在 4-32GB 最佳,ZGC 从 4GB 起也能跑,堆可到 16TB。
第三层是业务特征。批处理追求吞吐用 ParallelGC,Web 服务追求停顿用 G1,低延迟+大堆用 ZGC。
GC 演进主线:Serial(单线程,为快)→ ParallelGC(并行化,求吞吐,JDK 8 默认)→ CMS(并发标记-清除,求停顿,但有碎片+Concurrent Mode Failure,JDK 14 弃用)→ G1(Region 结构+可预测停顿,JDK 9+ 默认)→ ZGC/Shenandoah(读屏障+多映射,STW <10ms,TB 级堆,JDK 15+)。
我们项目用的是 G1,理由是三层漏斗刚好命中:JDK 8 支持 G1,堆 16-32GB 在 G1 最佳区间,Web 服务通用场景停顿优先。G1 的核心优势是Region 结构(堆切成等大小 Region,回收选最脏的优先)+可预测停顿(-XX:MaxGCPauseMillis=200)+Mixed GC 避免退化 FullGC。配置简单、稳定性好、社区成熟。
ZGC 为什么暂时不用?我们 JDK 8 用不了(ZGC JDK 15 GA),堆也没到需要 ZGC 的规模(32GB+)。等升级 JDK 17/21 时再评估——特别是如果 P99 停顿敏感或堆超过 32GB,ZGC 的 <10ms 停顿就是巨大优势。
核心调优参数:-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=45 -Xms=Xmx(避免动态扩缩)。堆大小建议 Xms=Xmx 减少扩缩开销。
怎么评估 GC 是否合适? 关键看三个指标:GC 频率(每秒数次 YoungGC 正常,FullGC 应极少)、STW 时长 P99(生产要求 <200ms)、堆使用率(FullGC 后仍 >70% 说明有泄漏)。工具用 -Xlog:gc* 打日志、JFR jdk.GCPhasePause 事件、Prometheus + JVMExporter 打 Grafana 大盘。
一句话总结:GC 器没有最好的,只有最适合的——按 JDK 版本定可用范围,按内存规模定候选,按业务特征定取舍,最后靠 GC 日志和 JFR 数据验证。G1 是绝大多数 Web 服务的默认答案,ZGC 是大堆低延迟的进阶选择。