文章目录
- JVM垃圾回收:ZGC与Shenandoah 系统性知识体系(2026超高频)
- 一、整体定位与发展历程
- 1.1 低延迟垃圾收集器诞生背景
- 1.2 发展历程对比
- 二、ZGC核心原理与关键技术
- 2.1 核心设计思想
- 2.2 着色指针技术详解
- 2.3 读屏障技术
- 2.4 ZGC垃圾回收周期
- 三、Shenandoah核心原理与关键技术
- 3.1 核心设计思想
- 3.2 Brooks指针技术
- 3.3 Shenandoah垃圾回收周期
- 四、低延迟特性深度解析
- 4.1 低延迟的核心保障
- 4.2 典型停顿时间对比
- 4.3 吞吐量与延迟的权衡
- 五、JDK21分代ZGC优化(2026超高频考点)
- 5.1 分代ZGC诞生背景
- 5.2 分代ZGC内存布局
- 5.3 分代ZGC关键优化点
- 5.4 分代ZGC性能提升
- 六、ZGC vs Shenandoah 深度对比
- 6.1 核心技术对比
- 6.2 性能对比
- 6.3 适用场景
- 七、调优指南与最佳实践
- 7.1 ZGC调优参数
- 7.2 Shenandoah调优参数
- 7.3 最佳实践
- 八、2026面试高频考点
- 8.1 基础概念题
- 8.2 原理深度题
- 8.3 对比分析题
- 8.4 调优实践题
- 九、未来发展趋势
- ZGC与Shenandoah 面试背诵问答卡片(2026超高频)
- 一、基础概念题(必背)
- 二、核心原理题(高频)
- 三、JDK21分代ZGC专题(2026超高频)
- 四、对比分析题(高频)
- 五、调优实践题(高频)
JVM垃圾回收:ZGC与Shenandoah 系统性知识体系(2026超高频)
一、整体定位与发展历程
1.1 低延迟垃圾收集器诞生背景
- 痛点:传统收集器(Serial、Parallel、CMS、G1)在大堆(100GB+)场景下停顿时间过长(G1通常在100ms以上)
- 目标:将GC停顿时间控制在亚毫秒级(<10ms),且停顿时间不随堆大小增长而增加
- 核心设计理念:并发执行所有耗时操作,仅在必要时进行极短暂的STW(Stop-The-World)
1.2 发展历程对比
| 特性 | ZGC | Shenandoah |
|---|---|---|
| 发起方 | Oracle | Red Hat |
| 首次发布 | JDK 11(实验性) | JDK 12(实验性) |
| 正式转正 | JDK 15 | JDK 15 |
| 分代实现 | JDK 21(正式) | JDK 17(实验性),JDK 21(正式) |
| 最新版本 | JDK 23(持续优化) | JDK 23(持续优化) |
| 支持平台 | x86_64、AArch64、Windows、Linux、macOS | 全平台支持 |
二、ZGC核心原理与关键技术
2.1 核心设计思想
- 基于Region的内存布局:将堆划分为多个大小相等的Region(2MB/32MB/256MB,根据堆大小自动选择)
- 着色指针(Colored Pointer):利用64位指针的高几位存储对象的元数据信息
- 读屏障(Load Barrier):在读取对象引用时执行的一小段代码,用于处理并发转移
- 多映射内存(Multi-Mapping):将同一块物理内存映射到多个虚拟地址空间
2.2 着色指针技术详解
ZGC利用64位指针的第42-45位存储4个标记位:
- Marked0(M0):第42位,标记阶段0的存活对象
- Marked1(M1):第43位,标记阶段1的存活对象
- Remapped(R):第44位,对象已被转移到新地址
- Finalizable(F):第45位,对象有finalize方法需要执行
优势:
- 无需在对象头中存储标记位,节省内存
- 标记和转移操作可以完全并发进行
- 转移完成后无需更新所有引用,通过读屏障自动修正
2.3 读屏障技术
当应用线程读取一个对象引用时,ZGC会自动插入读屏障:
// 伪代码 Object load(Object* ref) { if (ref has Remapped bit set) { return ref; } // 尝试将引用更新到新地址 Object* new_ref = get_forwarded_address(ref); // CAS更新引用 if (CAS(ref, new_ref)) { return new_ref; } // 其他线程已经更新,重新读取 return load(ref); }关键特性:
- 仅在读操作时触发,写操作无需屏障
- 开销极低(现代CPU可预测分支)
- 实现了并发转移,转移过程中应用线程可以继续运行
2.4 ZGC垃圾回收周期
ZGC的回收周期分为4个主要阶段,其中只有两个极短的STW阶段:
- 初始标记(STW):标记GC Roots直接引用的对象,停顿时间<1ms
- 并发标记:遍历对象图,标记所有存活对象,与应用线程并发执行
- 最终标记(STW):处理并发标记阶段的引用变化,停顿时间<1ms
- 并发转移:将存活对象复制到新的Region,同时通过读屏障修正引用
- 并发重映射:修正所有未被读屏障处理的引用(可与下一次标记合并)
三、Shenandoah核心原理与关键技术
3.1 核心设计思想
- 基于Region的内存布局:与ZGC类似,划分为多个大小相等的Region
- ** Brooks指针(Brooks Pointer)**:每个对象头中包含一个指向自身的转发指针
- 读写屏障:在读写对象引用时执行屏障操作
- 连接矩阵(Connection Matrix):记录Region之间的引用关系,优化跨代引用处理
3.2 Brooks指针技术
每个对象头中额外存储一个转发指针,初始时指向对象自身:
// 对象头结构 struct ObjectHeader { mark_word; // 标记字 klass_ptr; // 类指针 brooks_ptr; // 转发指针(Shenandoah特有) };工作原理:
- 当对象被转移时,将原对象的Brooks指针指向新对象
- 所有对原对象的访问都会通过Brooks指针重定向到新对象
- 转移完成后,后台线程逐步更新所有引用
3.3 Shenandoah垃圾回收周期
Shenandoah的回收周期比ZGC更复杂,包含9个阶段:
- 初始标记(STW):标记GC Roots直接引用的对象
- 并发标记:遍历对象图,标记所有存活对象
- 最终标记(STW):处理并发标记阶段的引用变化
- 并发清理:回收没有存活对象的Region
- 并发准备转移:选择需要转移的Region集合
- 初始转移(STW):将GC Roots引用更新到新地址
- 并发转移:将存活对象复制到新的Region
- 最终更新引用(STW):更新所有剩余的引用
- 并发清理:回收转移完成后的旧Region
四、低延迟特性深度解析
4.1 低延迟的核心保障
- 全并发执行:所有耗时操作(标记、转移、清理)均与应用线程并发执行
- 停顿时间与堆大小无关:停顿时间仅与GC Roots数量和引用变化率有关
- 增量式处理:将大任务拆分为多个小任务,避免长时间STW
- 预测性调度:根据应用负载动态调整GC线程数量和执行时间
4.2 典型停顿时间对比
| 收集器 | 典型停顿时间 | 最大停顿时间 | 与堆大小关系 |
|---|---|---|---|
| G1 | 100-200ms | 500ms+ | 随堆增大而增加 |
| ZGC(不分代) | <1ms | <10ms | 几乎无关 |
| Shenandoah | <10ms | <20ms | 几乎无关 |
| ZGC(分代,JDK21+) | <1ms | <5ms | 几乎无关 |
4.3 吞吐量与延迟的权衡
- ZGC/Shenandoah vs G1:吞吐量略低(约5-15%),但延迟降低一个数量级
- ZGC vs Shenandoah:ZGC的平均停顿时间更短,Shenandoah的吞吐量略高
- 适用场景:对延迟敏感的应用(微服务、实时系统、金融交易)优先选择ZGC/Shenandoah
五、JDK21分代ZGC优化(2026超高频考点)
5.1 分代ZGC诞生背景
- 不分代ZGC的痛点:
- 每次回收都需要扫描整个堆,大堆场景下CPU开销高
- 无法利用"弱分代假说",导致不必要的对象复制
- 内存碎片问题仍然存在,需要定期执行Full GC
- 分代设计的优势:
- 新生代对象存活率低,回收效率高
- 老年代对象存活率高,回收频率低
- 整体吞吐量提升,CPU开销降低
5.2 分代ZGC内存布局
分代ZGC将堆划分为两个代:
- 新生代(Young Generation):
- 进一步划分为Eden区和Survivor区
- 采用复制算法进行回收
- 回收频率高,每次回收只处理新生代
- 老年代(Old Generation):
- 采用标记-整理算法进行回收
- 回收频率低,只有在老年代满时才触发
- 支持并发转移和并发标记
5.3 分代ZGC关键优化点
双标记位循环使用:
- 新生代和老年代使用不同的标记位(M0/M1)
- 避免了代之间的标记干扰
- 实现了代独立回收
跨代引用处理:
- 使用**卡表(Card Table)**记录老年代对新生代的引用
- 新生代回收时只需扫描卡表中的脏卡,无需扫描整个老年代
- 卡表更新通过写屏障实现,开销极低
晋升机制优化:
- 对象在Survivor区经历一定次数的回收后晋升到老年代
- 大对象直接分配在老年代
- 支持动态调整晋升阈值
GC触发条件优化:
- 新生代:Eden区满时触发
- 老年代:老年代使用率达到阈值时触发
- 支持自适应调整阈值,根据应用负载动态优化
5.4 分代ZGC性能提升
- 吞吐量提升:相比不分代ZGC,吞吐量提升20-50%
- CPU开销降低:新生代回收只扫描小部分堆,CPU使用率降低
- 内存利用率提高:分代设计减少了内存碎片
- 停顿时间保持:仍然保持亚毫秒级停顿,最大停顿时间<5ms
六、ZGC vs Shenandoah 深度对比
6.1 核心技术对比
| 特性 | ZGC | Shenandoah |
|---|---|---|
| 指针技术 | 着色指针(利用指针高位) | Brooks指针(对象头中) |
| 屏障类型 | 仅读屏障 | 读写屏障 |
| 内存布局 | 基于Region | 基于Region |
| 转移方式 | 并发转移+读屏障修正 | 并发转移+Brooks指针重定向 |
| 分代实现 | JDK21正式支持 | JDK21正式支持 |
6.2 性能对比
| 指标 | ZGC | Shenandoah | G1 |
|---|---|---|---|
| 平均停顿时间 | <1ms | <10ms | 100-200ms |
| 最大停顿时间 | <5ms(分代) | <20ms | 500ms+ |
| 吞吐量 | 中等(分代后大幅提升) | 中等偏高 | 高 |
| 内存开销 | 低(着色指针) | 中等(Brooks指针) | 低 |
| CPU开销 | 中等 | 中等偏高 | 低 |
6.3 适用场景
ZGC优先选择:
- 对延迟要求极高的应用(<10ms)
- 大堆场景(100GB+)
- 微服务、实时系统、金融交易
- JDK21及以上版本
Shenandoah优先选择:
- 对吞吐量有一定要求的低延迟应用
- 需要全平台支持的场景
- JDK17及以上版本
G1优先选择:
- 对吞吐量要求高,延迟要求一般的应用
- 堆大小较小(<32GB)的场景
- JDK8及以下版本
七、调优指南与最佳实践
7.1 ZGC调优参数
# 启用ZGC-XX:+UseZGC# 启用分代ZGC(JDK21+,默认开启)-XX:+ZGenerational# 设置堆大小-Xms16g-Xmx16g# 设置GC线程数(默认等于CPU核心数)-XX:ConcGCThreads=4# 设置最大停顿时间目标(默认200ms,ZGC会尽力满足)-XX:MaxGCPauseMillis=5# 启用大页支持(提升性能)-XX:+UseLargePages7.2 Shenandoah调优参数
# 启用Shenandoah-XX:+UseShenandoahGC# 启用分代Shenandoah(JDK21+)-XX:+ShenandoahGenerational# 设置GC线程数-XX:ConcGCThreads=4# 设置最大停顿时间目标-XX:MaxGCPauseMillis=107.3 最佳实践
- 堆大小设置:建议设置为物理内存的50-70%,避免堆过小导致频繁GC
- GC线程数:默认值通常足够,不要设置过多,避免与应用线程竞争CPU
- 大页支持:强烈建议启用大页,可显著提升性能
- JDK版本:尽可能使用最新的JDK版本(JDK21+),分代ZGC性能提升明显
- 监控指标:重点关注GC停顿时间、GC频率、吞吐量和CPU使用率
八、2026面试高频考点
8.1 基础概念题
- ZGC和Shenandoah的核心设计思想是什么?
- 着色指针和Brooks指针的区别是什么?
- 读屏障和写屏障在ZGC和Shenandoah中的作用是什么?
- 为什么ZGC的停顿时间与堆大小无关?
8.2 原理深度题
- 详细描述ZGC的垃圾回收周期,指出哪些阶段是STW的?
- 分代ZGC解决了不分代ZGC的哪些问题?
- 分代ZGC的内存布局是怎样的?跨代引用如何处理?
- Shenandoah的连接矩阵有什么作用?
8.3 对比分析题
- ZGC vs Shenandoah vs G1的优缺点对比?
- 分代ZGC相比不分代ZGC有哪些性能提升?
- 什么场景下应该选择ZGC,什么场景下应该选择Shenandoah?
8.4 调优实践题
- 如何开启分代ZGC?有哪些关键调优参数?
- 如果ZGC的停顿时间过长,应该如何排查和调优?
- 大堆场景下使用ZGC有哪些注意事项?
九、未来发展趋势
- 进一步降低停顿时间:目标是实现亚微秒级停顿
- 自动调优增强:根据应用负载自动调整GC参数
- 硬件加速:利用现代CPU的特性(如AVX-512)加速GC操作
- 统一垃圾回收框架:JDK正在逐步统一不同收集器的代码框架
- 弹性堆大小:支持根据应用负载动态调整堆大小
ZGC与Shenandoah 面试背诵问答卡片(2026超高频)
一、基础概念题(必背)
问题:ZGC和Shenandoah诞生的核心背景与设计目标是什么?
答案:诞生背景是传统收集器(G1等)在100GB+大堆场景下停顿时间过长(>100ms)且随堆增大而增加。核心设计目标是将GC停顿时间控制在亚毫秒级(<10ms),且停顿时间与堆大小无关,通过并发执行所有耗时操作实现。问题:ZGC的三大核心技术是什么?
答案:① 基于Region的内存布局(2MB/32MB/256MB自动选择);② 着色指针(利用64位指针高位存储对象元数据);③ 读屏障(仅在读操作时触发,实现并发转移)。问题:Shenandoah的三大核心技术是什么?
答案:① 基于Region的内存布局;② Brooks指针(对象头中存储转发指针);③ 读写屏障(读写操作均触发,配合Brooks指针实现重定向)。问题:为什么ZGC和Shenandoah的停顿时间与堆大小无关?
答案:它们的STW阶段仅处理GC Roots和引用变化,不扫描整个堆。所有耗时的标记、转移、清理操作均与应用线程并发执行,停顿时间只与GC Roots数量和引用变化率有关,与堆总大小无关。
二、核心原理题(高频)
问题:详细解释ZGC的着色指针技术,四个标记位分别是什么?
答案:ZGC利用64位指针的第42-45位存储4个标记位:- M0(第42位):标记阶段0的存活对象
- M1(第43位):标记阶段1的存活对象
- Remapped(第44位):对象已被转移到新地址
- Finalizable(第45位):对象有finalize方法需要执行
优势:无需对象头存储标记位,标记和转移可完全并发,转移后无需立即更新所有引用。
问题:ZGC的读屏障是如何工作的?
答案:当应用线程读取对象引用时,自动插入读屏障:- 检查引用的Remapped位是否已设置,已设置则直接返回
- 未设置则获取对象的转发地址
- 通过CAS原子更新引用到新地址
- 返回新地址的对象
特点:仅读操作触发,开销极低,实现了无停顿并发转移。
问题:ZGC的垃圾回收周期包含哪些阶段?哪些是STW的?
答案:共5个阶段,仅2个极短STW阶段:- 初始标记(STW,<1ms):标记GC Roots直接引用
- 并发标记:遍历对象图标记所有存活对象
- 最终标记(STW,<1ms):处理并发标记期间的引用变化
- 并发转移:将存活对象复制到新Region
- 并发重映射:修正剩余未更新的引用(可与下一次标记合并)
问题:Shenandoah的Brooks指针是如何工作的?
答案:每个对象头额外存储一个转发指针,初始指向自身。当对象被转移时,将原对象的Brooks指针指向新对象。所有对原对象的访问都会通过Brooks指针自动重定向到新对象,转移完成后后台线程逐步更新所有引用。问题:Shenandoah的垃圾回收周期包含哪些关键STW阶段?
答案:共4个STW阶段,均为亚毫秒级:- 初始标记:标记GC Roots直接引用
- 最终标记:处理并发标记的引用变化
- 初始转移:更新GC Roots引用到新地址
- 最终更新引用:更新所有剩余的引用
三、JDK21分代ZGC专题(2026超高频)
问题:不分代ZGC存在哪些核心痛点?分代ZGC解决了什么问题?
答案:不分代ZGC痛点:- 每次回收扫描整个堆,大堆CPU开销高
- 无法利用弱分代假说,大量短生命周期对象被重复复制
- 内存碎片问题仍存在,需定期Full GC
分代ZGC解决:吞吐量提升20-50%,CPU开销降低,内存利用率提高,同时保持亚毫秒级停顿。
问题:分代ZGC的内存布局是怎样的?
答案:将堆划分为两个独立代:- 新生代:进一步分为Eden区和Survivor区,采用复制算法,回收频率高
- 老年代:采用标记-整理算法,回收频率低,仅在老年代满时触发
两代使用不同的标记位(M0/M1)循环,实现代独立回收。
问题:分代ZGC如何处理跨代引用?
答案:使用**卡表(Card Table)**记录老年代对新生代的引用。老年代被划分为512字节的卡,当老年代对象引用新生代对象时,对应的卡被标记为脏卡。新生代回收时只需扫描脏卡,无需扫描整个老年代,卡表更新通过写屏障实现。问题:分代ZGC相比不分代ZGC有哪些关键性能提升?
答案:- 吞吐量提升20-50%(核心提升)
- CPU使用率降低30%左右(新生代回收仅扫描小部分堆)
- 内存碎片显著减少,内存利用率提高
- 最大停顿时间从<10ms进一步降低到<5ms
- 大幅降低了大堆场景下的Full GC概率
问题:JDK21中分代ZGC是默认开启的吗?如何手动开启/关闭?
答案:JDK21及以上版本中,分代ZGC是默认开启的。手动开启:-XX:+ZGenerational;手动关闭(使用不分代ZGC):-XX:-ZGenerational。
四、对比分析题(高频)
问题:ZGC和Shenandoah的核心技术区别是什么?
答案:维度 ZGC Shenandoah 指针技术 着色指针(利用指针高位) Brooks指针(对象头中) 屏障类型 仅读屏障 读写屏障 内存开销 低(无额外对象头开销) 中等(每个对象多一个指针) 平均停顿 <1ms <10ms 吞吐量 中等(分代后大幅提升) 中等偏高 问题:ZGC、Shenandoah和G1的适用场景分别是什么?
答案:- ZGC:对延迟要求极高(<10ms)的大堆(100GB+)场景,如金融交易、实时系统、微服务,推荐JDK21+
- Shenandoah:对吞吐量有一定要求的低延迟应用,需要全平台支持的场景,推荐JDK21+
- G1:对吞吐量要求高、延迟要求一般(100-200ms)的中小堆(<32GB)场景,或JDK8及以下版本
问题:分代ZGC和分代Shenandoah哪个更推荐使用?
答案:JDK21及以上版本优先推荐分代ZGC。它的平均停顿时间更短(<1ms vs <10ms),内存开销更低,且经过Oracle更充分的优化和测试。分代Shenandoah适合需要全平台支持或对Red Hat生态有依赖的场景。
五、调优实践题(高频)
问题:开启ZGC的核心JVM参数有哪些?
答案:# 启用ZGC(JDK15+)-XX:+UseZGC# 启用分代ZGC(JDK21+,默认开启)-XX:+ZGenerational# 设置堆大小(建议物理内存的50-70%)-Xms16g-Xmx16g# 设置GC线程数(默认等于CPU核心数)-XX:ConcGCThreads=4# 设置最大停顿时间目标(默认200ms,ZGC会尽力满足)-XX:MaxGCPauseMillis=5# 启用大页支持(强烈推荐,性能提升显著)-XX:+UseLargePages问题:如果ZGC的停顿时间过长,应该如何排查和调优?
答案:- 检查GC日志,确认是哪个STW阶段耗时过长
- 若初始标记/最终标记过长:减少GC Roots数量(如减少线程数)
- 若并发阶段跟不上应用速度:增加
ConcGCThreads数量 - 适当调大堆大小,减少GC频率
- 启用大页支持,降低内存访问延迟
- 升级到最新JDK版本(JDK21+分代ZGC停顿更短)
问题:大堆场景下使用ZGC有哪些最佳实践?
答案:- 必须使用JDK21及以上版本的分代ZGC
- 堆大小建议设置为物理内存的50-70%,避免过小导致频繁GC
- 启用大页支持(透明大页或巨页)
- GC线程数设置为CPU核心数的1/4-1/2,避免与应用线程竞争
- 不要设置过小的
MaxGCPauseMillis,否则会导致GC频率过高 - 重点监控GC频率、停顿时间和CPU使用率