侧边栏壁纸
博主头像
牧云

怀璧慎显,博识谨言。

  • 累计撰写 178 篇文章
  • 累计创建 18 个标签
  • 累计收到 8 条评论

目 录CONTENT

文章目录

JVM GC 演进之谜:为何“低延迟”成为绝对主线?

秋之牧云
2026-05-08 / 0 评论 / 0 点赞 / 6 阅读 / 0 字

在 Java 虚拟机的世界里,垃圾收集(GC)一直是一个既神秘又核心的话题。很多开发者会发现一个有趣的现象:近十年来,JVM GC 的每一次重大革新——从 CMS 到 G1,再到 ZGC 和 Shenandoah——似乎都在死磕同一个目标:降低停顿时间(Low Latency)

这就引出了两个灵魂拷问:

  1. 为什么 GC 的演进主线全是“低延迟”,难道大家不关心吞吐量了吗?

  2. 为什么再也没有出现新的、专门针对“高吞吐”优化的垃圾收集器?

要回答这些问题,我们需要先厘清两个核心概念:吞吐量与延迟,然后从技术瓶颈和业务变迁两个维度深入剖析。

一、 基础概念:吞吐量 vs 延迟

在讨论 GC 之前,我们必须明确这两个性能指标的定义及其计算方式,因为它们代表了两种截然不同的优化方向。

1. 吞吐量 (Throughput)

  • 定义:系统在单位时间内处理请求的数量或完成的工作量。它关注的是“整体效率”,即 CPU 有多少比例的时间是用于运行用户代码,而不是用于 GC。

  • 计算公式

textThroughput=总请求数 (N)总耗时 (T)text{Throughput} = \frac{\text{总请求数 (N)}}{\text{总耗时 (T)}}

- 例如:1 秒内处理了 10,000 个请求,吞吐量即为 10,000 QPS。
- 在 GC 语境下,吞吐量通常指:
应用运行时间应用运行时间+GC 时间\frac{\text{应用运行时间}}{\text{应用运行时间} + \text{GC 时间}}

2. 延迟 (Latency)

  • 定义单次请求从发出到收到响应所花费的时间。它关注的是“响应速度”,直接决定用户的直观体验。

  • 计算公式

Latency=总耗时 (T)总请求数 (N)\text{Latency} = \frac{\text{总耗时 (T)}}{\text{总请求数 (N)}}

- 例如:处理 1,000 个请求总共花了 2 秒,平均延迟为 2ms。
- 注意:在 GC 语境下,我们更关注最大停顿时间(Max Pause Time)或 P99 延迟,因为一次长时间的 Stop-The-World (STW) 就会导致单个请求超时。

3. 两者的关系

吞吐量和延迟往往存在博弈关系。根据利特尔法则(Little’s Law),在并发度固定的情况下,降低延迟通常有助于提高吞吐量;但在 GC 场景下,为了追求极致吞吐而采用的大规模并行 STW,往往会牺牲单次请求的延迟稳定性。

二、 为什么没有新的“高吞吐”收集器?

事实上,JVM 中一直存在高吞吐收集器,最典型的代表就是 Parallel Scavenge / Parallel Old。但为什么近年来没有新的竞争者出现?原因在于高吞吐优化已触及“天花板”

1. 算法效率已趋极致

Parallel GC 的核心逻辑非常简单粗暴:在 STW 期间,利用所有可用的 CPU 核心并行进行垃圾回收。

  • 优势:由于不需要处理复杂的线程同步、并发标记或读写屏障,它的额外开销极小,CPU 几乎全部用于“干活”。

  • 现状:在大多数场景下,Parallel GC 已经能将 GC 导致的吞吐量损失控制在极低水平(例如 99% 以上的吞吐量)。要从 99% 提升到 99.5%,需要付出巨大的工程代价,但业务收益微乎其微。

2. 硬件红利自然覆盖

随着摩尔定律的发展,CPU 核心数越来越多,内存带宽越来越大。

  • 即使 GC 算法不变,硬件的提升也会自然带来吞吐量的增长。

  • 对于真正需要极致吞吐的离线批处理任务(如 Hadoop/Spark),现有的 Parallel GC 已经足够好。如果不够,通常的做法是横向扩展(加机器),而不是等待一个新的 GC 算法。

3. 边际效应递减

开发一个新的高吞吐收集器需要极高的复杂度,但市场回报极低。因为“足够快”的吞吐方案已经存在,且通过架构调整(如异步化、批量处理)也能解决大部分吞吐瓶颈。

三、 为什么“低延迟”成为演进主线?

既然高吞吐已经“够用”,为什么我们要死磕低延迟?这是因为业务场景变了,且大内存时代带来了新的痛点

1. 业务模式从“批处理”转向“实时交互”

  • 过去:Java 常用于后台 ERP、批处理系统。这些场景对单次停顿不敏感,只要整体跑完得快就行。

  • 现在:微服务、云计算、高频交易、实时推荐系统成为主流。

    • SLA 约束:互联网服务要求 P99 延迟在毫秒级。

    • 雪崩效应:在一次长 STW 期间,服务无法响应,负载均衡器会将该节点剔除,导致流量瞬间涌向其他节点,可能引发集群雪崩。

    • 用户体验:前端页面的卡顿、视频流的中断,都直接归咎于后端的延迟抖动。

2. “大内存”时代的致命缺陷

随着服务器内存从 GB 级迈向 TB 级,传统 GC 面临巨大挑战:

  • STW 时间与堆大小成正比:在 Parallel GC 或 CMS 中,标记和整理阶段需要遍历对象。当堆内存达到数百 GB 时,即使算法再高效,STW 也可能长达数秒甚至数十秒。

  • 不可接受性:对于在线服务,几秒钟的停顿是灾难性的。

3. 技术突破点:解耦堆大小与停顿时间

为了解决上述问题,新一代 GC(ZGC, Shenandoah)引入了革命性的技术:

  • 并发标记与整理:将耗时的操作移到与应用线程并发执行。

  • 染色指针(Colored Pointers)/ 读屏障(Read Barriers):通过元数据技术,让 GC 能够在不暂停应用线程的情况下更新对象引用。

  • 成果:无论堆是 10GB 还是 10TB,STW 时间都能控制在毫秒级(<10ms)

四、 主流收集器的权衡选择

理解了这个背景,我们就可以清晰地看待当前的收集器格局:

收集器

核心策略

适用场景

现状

Parallel GC

高吞吐,STW 并行

后台批处理、科学计算

稳定,无新替代者

G1

平衡,分区可预测延迟

主流 Web 服务、大内存

JDK 9+ 默认,广泛使用

ZGC / Shenandoah

极低延迟,全程并发

实时交易、超大堆、微服务

未来趋势,快速成熟

五、 总结

JVM GC 的演进并非“抛弃”了高吞吐,而是在高吞吐需求已被现有技术和硬件满足的前提下,集中火力解决“大内存下的低延迟”这一更棘手、更具业务价值的难题

  • 没有新的高吞吐收集器,是因为 Parallel GC 已经做到了极致,且硬件升级在不断兜底。

  • 低延迟成为主线,是因为实时业务对稳定性的要求越来越高,且只有并发技术才能解决 TB 级内存下的停顿问题。

对于开发者而言,选择 GC 不再是盲目追求最新,而是基于业务场景的理性权衡:

  • 如果是离线计算,Parallel GC 依然是王者。

  • 如果是在线服务,G1 是稳健的默认选择。

  • 如果对延迟极度敏感,ZGC 将是你的终极武器。

这场关于时间与空间的博弈,仍在继续,但目标始终未变:让 Java 应用跑得更快、更稳、更平滑。

0

评论区