Java的自动内存管理(垃圾回收,GC)是其区别于C++的重要特性之一。二十多年来,Java虚拟机(JVM)的垃圾回收算法经历了从串行、并行到并发、低延迟的演进,以适应不同硬件和业务场景。本文将追溯GC的发展历程,剖析Serial、Parallel、CMS、G1、ZGC等核心收集器的设计原理,对比各算法在吞吐量、延迟和内存占用方面的权衡,并给出选型建议。
参考:https://vrhyh.cn/category/yundong.html
第一代:Serial和Parallel GC
JDK 1.3之前的Serial GC使用单线程进行垃圾回收,会触发STW(Stop-The-World)暂停所有应用线程。它在小型应用和客户端环境下简单高效,但多核服务器下无法利用CPU资源。Parallel GC(也称为吞吐量优先收集器)在JDK 1.4引入,使用多线程进行Minor GC和Full GC,极大提升了吞吐量。其目标是通过调整堆大小和新生代比例,最大化应用程序运行时间占比。Parallel GC是JDK 8之前的默认收集器,适合后台计算、批处理等对延迟不敏感的任务。
第二代:CMS(Concurrent Mark Sweep)
随着对响应时间要求的提高(如Web应用、实时系统),CMS收集器应运而生。CMS在标记清除阶段与应用程序并发执行,减少STW时间。它的过程分为:初始标记(STW,标记GC Roots直接可达对象)、并发标记(与应用线程并发,标记所有存活对象)、重新标记(STW,修正并发标记期间变化的对象)、并发清除(清除死亡对象)。CMS的问题在于:容易产生内存碎片,导致Full GC退化为Serial GC;对CPU资源敏感;无法处理浮动垃圾,可能发生并发模式失败。尽管如此,CMS在JDK 1.4到JDK 8期间广受欢迎。
第三代:G1(Garbage First)
JDK 7u4引入G1,旨在替代CMS,提供可预测的停顿时间模型。G1将堆划分为多个大小相等的Region(1-32MB),每个Region可以是Eden、Survivor或Old。G1通过追踪每个Region的垃圾堆积程度,优先回收垃圾最多的Region(Garbage First)。它的停顿时间可配置(-XX:MaxGCPauseMillis),通过控制每次回收的Region数量来近似达成。G1的标记阶段与CMS类似,但使用了SATB(Snapshot-At-The-Beginning)算法避免漏标。G1在JDK 9成为默认收集器,适合大堆(6GB以上)且对延迟有要求的场景。
参考:https://oqmyh.cn/category/hufu-chengfen.html
第四代:ZGC和Shenandoah
为了应对毫秒级延迟需求(如内存超过100GB的实时系统),JDK 11引入了ZGC(以及OpenJDK的Shenandoah)。ZGC的核心技术是染色指针(Colored Pointers)和读屏障(Load Barrier)。染色指针在64位指针的高位存储GC状态信息,使得GC可以在不暂停应用的情况下移动对象。读屏障在每次读操作时检查对象指针的颜色,若需要则执行修正操作。ZGC的停顿时间不超过10ms,且不随堆大小增加而增加(可支持TB级堆)。但是ZGC牺牲了部分吞吐量(约5-15%),且对内存占用略有增加。JDK 15后ZGC成为生产可用,JDK 21中ZGC支持分代模式,进一步提升了性能。
算法对比总结:
Serial:单线程,适合小型应用(<100MB堆)。
Parallel:多线程,高吞吐,适合计算密集型、离线任务。
CMS:低延迟,但有碎片和CPU敏感问题,已废弃。
G1:平衡吞吐和延迟,适合中等大小堆(6-64GB),默认推荐。
ZGC:极低延迟,超大堆场景,适合交互式服务、内存数据库。
除了上述收集器,还有Epsilon(无操作GC,用于测试)、OpenJ9的GenCon等。选择GC需要根据应用的负载特性:对响应时间敏感的Web服务,推荐G1或ZGC;批处理报表任务,Parallel即可;内存小于4GB,考虑G1或Parallel。JDK 21及以后,ZGC已非常成熟,可替代G1用于大部分低延迟场景。
调优实践:
设置合理的堆大小(-Xms和-Xmx相等,避免扩容抖动)。
对于G1,调整MaxGCPauseMillis(默认200ms),太小会导致GC过于频繁。
启用GC日志(-Xlog:gc*:file=gc.log)分析停顿原因。
避免显式调用System.gc()(可用-XX:+DisableExplicitGC禁用)。
对于大内存机器,使用ZGC时注意指针压缩(-XX:+UseZGC需要-XX:-UseCompressedOops?实际上ZGC不支持压缩Oops,但JDK 14后部分支持)。
总之,Java GC的演进反映了应用需求从吞吐量优先向延迟敏感的转变。理解每种GC的算法细节,才能针对业务场景做出最合适的配置。
参考:https://npqev.cn/