§2.1.3 各JDK版本垃圾回收器
考察意图:了解不同回收器的设计目标、适用场景和演进历史,能根据业务场景推荐合适的回收器。
回答样板:
JDK发展史就是"降低GC停顿时间"的演进史——从秒级到毫秒级到亚毫秒级:
| 回收器 | 作用区域 | 算法 | 停顿特点 | 适用场景 | JDK版本 |
|---|---|---|---|---|---|
| Serial | 新生代 | 标记-复制 | 单线程STW,几十~几百ms | 单核、Client模式、几百MB堆 | 1.3+ |
| Serial Old | 老年代 | 标记-整理 | 单线程STW | 配合Serial | 1.3+ |
| Parallel Scavenge | 新生代 | 标记-复制 | 多线程STW,吞吐量优先 | 批处理、科学计算 | 1.4+ |
| Parallel Old | 老年代 | 标记-整理 | 多线程STW | 配合Parallel Scavenge | 1.6+ |
| CMS | 老年代 | 标记-清除 | 并发低停顿,最耗时的并发标记和并发清除阶段与用户线程并发 | 互联网Web应用(响应时间优先) | 1.5-14 |
| G1 | 整堆 | 标记-整理+复制 | 可预测停顿,Region化 | JDK 9默认、大堆(>4G)、对停顿有要求的服务端 | 7u4+ |
| ZGC | 整堆 | 染色指针+并发整理 | 亚毫秒级停顿(<1ms) | 超大堆(>16G)、极致低延迟 | 11+(实验) / 15+(生产) |
| Shenandoah | 整堆 | 并发复制+Brooks转发指针 | 低停顿 | 超大堆低延迟 | 12+(Red Hat主导) |
CMS(Concurrent Mark Sweep)——并发低停顿的先驱:
四个阶段:①初始标记(STW,扫描GC Roots直接引用,极快)→ ②并发标记(与用户线程并发,从GC Roots遍历整个对象图,最耗时)→ ③重新标记(STW,处理并发标记期间变动的引用,修正标记结果)→ ④并发清除(与用户线程并发)。
CMS的致命缺陷:
- 内存碎片——标记-清除不整理,碎片严重时触发Serial Old全堆整理(单线程,停顿秒级)
- 浮动垃圾——并发标记和清除期间用户线程产生的垃圾,只能等下次GC处理。需预留老年代空间给这些浮动垃圾,否则触发Concurrent Mode Failure,降级为Serial Old
- JDK 14已废弃,JDK 15移除
G1(Garbage First)——JDK 9+默认,划时代的分区回收器:
核心创新——将堆划分为等大小的Region(默认2048个,每个1~32MB),不再按物理分代。Region分为Eden、Survivor、Old、Humongous四种类型。
G1的混合GC(Mixed GC):不止回收新生代,还回收部分收益最高的老年代Region——每次回收"垃圾最多的Region",这也是Garbage First名字的来源。
关键机制:
- SATB(Snapshot At The Beginning):并发标记开始时的对象图快照,保证标记正确性
- RSet(Remembered Set):每个Region维护其他Region指向本Region的引用,避免全堆扫描
- 可预测停顿:
-XX:MaxGCPauseMillis设定目标(默认200ms),G1自动调整回收Region数量 - Humongous对象:超过Region大小50%的对象放入Humongous Region。大量短命大对象会引发频繁GC——需要调整Region大小或优化应用代码
G1 vs CMS直观对比:
- G1无内存碎片(标记-复制+整理),CMS有碎片问题
- G1停顿可预测可控,CMS的不确定性大
- G1的吞吐量略低于CMS(RSet维护开销),但换来更稳定的延迟
ZGC(Z Garbage Collector)——亚毫秒级停顿的终极方案:
核心创新——染色指针(Colored Pointer):在64位指针中嵌入GC状态信息(共4位:Finalizable/Remapped/Marked 1/Marked 0),标记阶段不修改对象头,只修改指针颜色。结合读屏障(Load Barrier)实现并发整理——应用线程读对象时发现指针颜色不对,触发地址修正,在访问过程中完成GC。
关键特性:
- 停顿时间不随堆大小增长——16MB堆<1ms,16TB堆也是<1ms
- 支持TB级堆、毫秒级回收、JDK 15生产可用
- 限制:仅Linux x64;吞吐量比G1低约5-10%(读屏障开销)
JDK版本与默认回收器对照速查:
| JDK版本 | 默认回收器 |
|---|---|
| JDK 8 | Parallel Scavenge + Parallel Old(吞吐量优先) |
| JDK 9-16 | G1 |
| JDK 17+ | G1(LTS) |
| JDK 21 | G1(新一代ZGC也在成熟,但G1仍是默认) |
面试官可能会问"你们项目用的什么回收器,为什么?":
项目部署在JDK 8上,默认Parallel Scavenge+Parallel Old。但我们实际指定了G1——原因:①堆内存配了4-8G,G1对这个区间的优化最好;②安全生产平台是面向客户的服务端应用,客户对接口响应时间敏感,G1的可预测停顿比Parallel的吞吐量优先更适合;③大量设备数据写入导致新生代对象产生速度快,Parallel的STW时间随堆增长接近1秒,G1的停顿控制在200ms以内。上了G1后加上 -XX:MaxGCPauseMillis=100调优,P99延迟从800ms降到了300ms。
陷阱提示:把G1说成简单的"分Region的CMS";不知道CMS在JDK 14已废弃;不知道JDK 8默认是Parallel而非G1;说不出ZGC的核心创新(染色指针);GC选型说不出业务场景理由。