"垃圾回收就像家务活,你不做它就堆积如山,做得太频繁又影响正事。"
—— 某位被GC折磨的程序员
前言:垃圾回收器的江湖恩怨
在Java的世界里,垃圾回收器(Garbage Collector,简称GC)就像是幕后的清洁工,默默地清理着程序运行时产生的"垃圾"。从JDK 1.0到如今的JDK 21,这些清洁工们经历了怎样的进化历程?让我们一起来看看这场持续了近30年的"清洁革命"。
第一章:Serial GC - 单打独斗的老兵
出生年月:JDK 1.0时代
性格特点:专一、简单、但有点慢
适用场景:单核CPU或小内存应用
Serial GC就像那个年代的老师傅,一个人包揽所有活儿。它的工作方式非常简单粗暴:
应用线程:我要分配内存!
Serial GC:等等,让我先停下所有工作,清理一下垃圾...
应用线程:😴(被迫休息)
Serial GC:好了,你可以继续了
应用线程:终于...(已经过去了好几秒)
优点:
• 简单可靠,不会出幺蛾子
• 内存占用小,适合嵌入式设备
• 单线程操作,不存在线程同步问题
缺点:
• STW(Stop The World)时间长,用户体验差
• 无法利用多核CPU优势
第二章:Parallel GC - 多线程的力量
出生年月:JDK 1.4引入,JDK 8成为默认选择
性格特点:团队合作,追求高吞吐量
默认使用版本:JDK 7、JDK 8
到了多核时代,Oracle意识到一个人干活太慢了,于是派出了一个团队——Parallel GC。
应用线程:我需要清理垃圾!
Parallel GC Team:收到!小A你清理新生代,小B你清理老年代,小C你整理内存碎片
应用线程:😴(还是要等,但时间短了很多)
Parallel GC Team:搞定!比Serial快3倍!
JDK 8的选择逻辑:
• 如果是服务器级别的机器(多核CPU + 大内存),默认使用Parallel GC
• 如果是客户端机器,可能使用Serial GC
优点:
• 充分利用多核CPU
• 高吞吐量,适合批处理应用
• 成熟稳定,经过长期验证
缺点:
• STW时间仍然不可控
• 延迟敏感的应用表现不佳
第三章:CMS GC - 追求低延迟的艺术家
出生年月:JDK 1.4引入,JDK 6成熟
性格特点:追求完美,但有强迫症
特殊技能:并发标记清除
CMS(Concurrent Mark Sweep)就像一个有洁癖的艺术家,它不能忍受长时间的停顿:
应用线程:我在运行业务逻辑...
CMS GC:我悄悄地标记一下垃圾对象(并发进行)
应用线程:我继续运行...(几乎感觉不到GC的存在)
CMS GC:好了,我快速清理一下(短暂停顿)
应用线程:咦?刚才停了一下吗?
优点:
• 大部分工作与应用线程并发进行
• 停顿时间短,用户体验好
• 适合响应时间敏感的应用
缺点:
• 不整理内存碎片,可能导致内存碎片化
• 并发执行会占用CPU资源
• 在JDK 9中被标记为废弃,JDK 14中完全移除
第四章:G1 GC - 新时代的全能选手
出生年月:JDK 7引入,JDK 9成为默认选择
性格特点:平衡大师,追求可预测性
默认使用版本:JDK 9-21(至今)
G1(Garbage First)就像一个经验丰富的项目经理,它把内存划分成许多小区域(Region),然后优先处理垃圾最多的区域:
G1 GC:让我看看...这个Region垃圾最多,先处理它
应用线程:我设定了停顿时间目标是100ms
G1 GC:收到!我会在100ms内完成,超时就停手
应用线程:真的吗?
G1 GC:我尽力而为!(90%的情况下都能做到)
为什么从JDK 9开始成为默认:
• 可以设置停顿时间目标(-XX:MaxGCPauseMillis)
• 适用于大堆内存(>4GB)
• 在吞吐量和延迟之间取得很好的平衡
优点:
• 可预测的停顿时间
• 适合大内存应用
• 自动调优能力强
• 并发标记和回收
缺点:
• 内存占用稍高(大约10-20%的额外开销)
• 小堆内存下性能不如Parallel GC
第五章:ZGC - 未来的超级英雄
出生年月:JDK 11引入(实验性),JDK 15正式发布
性格特点:极速,几乎感觉不到停顿
特殊能力:超低延迟(<10ms)
ZGC就像超级英雄,它的目标是让GC停顿时间永远不超过10毫秒,无论堆有多大:
应用线程:我有16TB的堆内存...
ZGC:没问题!
应用线程:停顿时间能控制在10ms以内吗?
ZGC:小case!我有彩色指针技术!
应用线程:什么是彩色指针?
ZGC:这个说来话长...(开始炫技)
技术特点:
• 使用彩色指针(Colored Pointers)技术
• 并发收集,几乎不停顿应用
• 支持TB级别的堆内存
第六章:各版本默认GC策略总结
JDK版本对照表:
JDK 1.0-6:默认Serial GC
特点:单线程,简单
适用场景:小应用,单核CPU
JDK 7-8:默认Parallel GC
特点:多线程,高吞吐
适用场景:批处理,多核服务器
JDK 9-21:默认G1 GC
特点:平衡延迟和吞吐
适用场景:大部分现代应用
JDK选择GC的智能逻辑
JDK在选择默认GC时会考虑以下因素:
1. 机器配置:
• 单核 → Serial GC
• 多核 + 小内存 → Parallel GC
• 多核 + 大内存 → G1 GC
2. 应用类型:
• 批处理 → Parallel GC
• 交互式应用 → G1 GC
• 超低延迟要求 → ZGC
第七章:如何选择合适的GC
选择指南
选择GC的决策流程:
开始选择GC → 判断堆内存大小 → 如果小于4GB,判断是否对延迟敏感 → 如果敏感选择G1 GC,否则选择Parallel GC
如果大于4GB → 判断是否有极低延迟要求 → 如果有选择ZGC,否则选择G1 GC
实际配置建议
小型应用(堆内存 < 2GB):
使用Parallel GC(JDK 8默认):
-XX:+UseParallelGC
或者使用G1 GC:
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
中大型应用(堆内存 2GB-32GB):
使用G1 GC(JDK 9+默认):
-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-XX:G1HeapRegionSize=16m
超大型应用(堆内存 > 32GB):
使用ZGC(JDK 11+):
-XX:+UseZGC
-XX:+UnlockExperimentalVMOptions # JDK 11-14需要
第八章:GC调优的江湖秘籍
秘籍一:监控先行
开启GC日志:
-Xloggc:gc.log
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
秘籍二:渐进调优
1. 先观察:收集至少一周的GC数据
2. 找瓶颈:识别是吞吐量问题还是延迟问题
3. 小步调整:一次只调一个参数
4. 验证效果:观察至少24小时
秘籍三:常见参数组合
追求吞吐量(Parallel GC):
-XX:+UseParallelGC
-XX:ParallelGCThreads=8
-XX:MaxGCPauseMillis=500
追求低延迟(G1 GC):
-XX:+UseG1GC
-XX:MaxGCPauseMillis=50
-XX:G1NewSizePercent=30
-XX:G1MaxNewSizePercent=40
第九章:未来展望
即将到来的新特性
1. 分代ZGC:JDK 21引入,结合分代收集和超低延迟
2. Shenandoah GC:RedHat贡献的低延迟收集器
3. 更智能的自适应调优:AI辅助的GC参数优化
发展趋势
• 更低的延迟:从秒级到毫秒级,再到微秒级
• 更高的吞吐量:充分利用现代硬件特性
• 更简单的配置:自适应调优,减少人工干预
• 更好的可观测性:实时监控和诊断工具
结语:GC的哲学思考
垃圾回收器的演进史,其实就是一部计算机科学追求性能与易用性平衡的历史。从Serial的简单粗暴,到Parallel的并行处理,再到G1的智能平衡,最后到ZGC的极致追求,每一步都体现了工程师们对完美的不懈追求。
正如一位资深Java工程师说过:"选择GC就像选择人生伴侣,没有完美的,只有最适合的。"
最后的建议
1. 不要过早优化:先让程序跑起来,再考虑GC调优
2. 测量驱动:用数据说话,不要凭感觉调优
3. 保持学习:GC技术在不断发展,要跟上时代步伐
4. 理解业务:GC调优要结合具体的业务场景
记住:最好的GC就是你感觉不到它存在的GC。
"在Java的世界里,我们都是垃圾制造者,而GC是我们最好的朋友。"
参考资料
• Oracle JDK Documentation
• OpenJDK GC Wiki
• 《深入理解Java虚拟机》- 周志明
• 《Java性能权威指南》- Scott Oaks






