在 Java 虚拟机的世界里,垃圾收集(GC)一直是一个既神秘又核心的话题。很多开发者会发现一个有趣的现象:近十年来,JVM GC 的每一次重大革新——从 CMS 到 G1,再到 ZGC 和 Shenandoah——似乎都在死磕同一个目标:降低停顿时间(Low Latency)。
这就引出了两个灵魂拷问:
为什么 GC 的演进主线全是“低延迟”,难道大家不关心吞吐量了吗?
为什么再也没有出现新的、专门针对“高吞吐”优化的垃圾收集器?
要回答这些问题,我们需要先厘清两个核心概念:吞吐量与延迟,然后从技术瓶颈和业务变迁两个维度深入剖析。
一、 基础概念:吞吐量 vs 延迟
在讨论 GC 之前,我们必须明确这两个性能指标的定义及其计算方式,因为它们代表了两种截然不同的优化方向。
1. 吞吐量 (Throughput)
定义:系统在单位时间内处理请求的数量或完成的工作量。它关注的是“整体效率”,即 CPU 有多少比例的时间是用于运行用户代码,而不是用于 GC。
计算公式:
- 例如:1 秒内处理了 10,000 个请求,吞吐量即为 10,000 QPS。
- 在 GC 语境下,吞吐量通常指:2. 延迟 (Latency)
定义:单次请求从发出到收到响应所花费的时间。它关注的是“响应速度”,直接决定用户的直观体验。
计算公式:
- 例如:处理 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)。
四、 主流收集器的权衡选择
理解了这个背景,我们就可以清晰地看待当前的收集器格局:
五、 总结
JVM GC 的演进并非“抛弃”了高吞吐,而是在高吞吐需求已被现有技术和硬件满足的前提下,集中火力解决“大内存下的低延迟”这一更棘手、更具业务价值的难题。
没有新的高吞吐收集器,是因为 Parallel GC 已经做到了极致,且硬件升级在不断兜底。
低延迟成为主线,是因为实时业务对稳定性的要求越来越高,且只有并发技术才能解决 TB 级内存下的停顿问题。
对于开发者而言,选择 GC 不再是盲目追求最新,而是基于业务场景的理性权衡:
如果是离线计算,Parallel GC 依然是王者。
如果是在线服务,G1 是稳健的默认选择。
如果对延迟极度敏感,ZGC 将是你的终极武器。
这场关于时间与空间的博弈,仍在继续,但目标始终未变:让 Java 应用跑得更快、更稳、更平滑。
评论区