JVM G1垃圾收集器 系统性知识体系(高频面试版)
一、G1垃圾收集器概述
1.1 定位与历史
G1垃圾收集器从JDK 7u4开始正式进入大家视野,到了JDK 9,它直接取代CMS成为默认的垃圾收集器。简单来说,G1是一款面向服务端应用、在低延迟和吞吐量之间努力找平衡的收集器。在HotSpot虚拟机的发展史上,它标志着一个重要的转折点——从传统的“物理分代”转向了“区域化逻辑分代”。
1.2 核心设计目标
G1的目标很明确,主要有三个:第一,可预测的停顿时间模型——你通过参数-XX:MaxGCPauseMillis告诉JVM你最多能忍受多长时间的停顿,G1会尽力去满足;第二,高吞吐量,在低延迟的前提下尽可能让应用反赌;第三,支持大堆,特别适合堆内存4GB以上的场景,而且它不需要扫描整个堆。
1.3 核心思想
G1的设计思路非常巧妙:化整为零——把Ja va堆拆成许多大小相等的独立区域(Region);收益优先——每次回收的时候,根据允许的停顿时间,专门挑垃圾最多的Region下手;分代思想还在,但不再是物理上把年轻代和老年代隔开,而是逻辑上的划分。
二、Region分区机制(G1的核心创新)
2.1 分区基本概念
G1把整个Ja va堆(年轻代+老年代)分成2048个Region,每个Region的大小在1MB到32MB之间,而且必须是2的幂次方。具体多大,JVM会自己根据堆总大小算,当然你也可以通过-XX:G1HeapRegionSize手动指定。
2.2 Region的类型
| 类型 | 说明 | 特点 |
|---|---|---|
| Eden Region | 年轻代Eden区 | 对象刚出生待的地方,默认占堆的5% |
| Survivor Region | 年轻代Survivor区 | 分S0和S1,默认占堆的1% |
| Old Region | 老年代区域 | 存放活了好几个轮回的对象 |
| Humongous Region | 巨型对象区域 | 专门放超过Region大小50%的大家伙 |
2.3 巨型对象(Humongous Object)处理
什么样的对象算巨型?单个Region容量的一半以上。这种对象直接分配到老年代的连续Humongous Region里,而且它不会在年轻代GC中被回收,只能在Mixed GC或Full GC里处理。由于要连续空间,容易产生内存碎片。好在JDK 8u40之后对巨型对象的回收效率做了优化。
2.4 Remembered Set(记忆集)
跨Region引用是个麻烦事,要解决它,G1的每个Region都配了一个Remembered Set。当一个Region里的对象引用了另一个Region里的对象,被引用的Region的Remembered Set就会记下这笔账。回收时,只要扫描对应Region的Remembered Set就行,不用翻遍整个堆。当然,天下没有免费的午餐,这个维护代价大约是堆内存的1%~5%。
三、Mixed GC机制(G1独有的回收方式)
3.1 为什么需要Mixed GC
以前的收集器,要么只回收年轻代,要么只回收老年代。G1不同,它搞了个Mixed GC,不仅回收年轻代,还挑一部分老年代Region一起处理。目的就是在用户指定的停顿时间里,尽可能多回收垃圾,避免触发耗时很长的Full GC。
3.2 Mixed GC的触发条件
当老年代占用率达到-XX:InitiatingHeapOccupancyPercent(默认45%)时,G1就会启动Mixed GC的流程。
3.3 回收集(Collection Set, CSet)
回收集就是Mixed GC中准备回收的Region集合。它包含所有年轻代Region,再加上一部分垃圾最多的老年代Region。怎么选?G1会给每个老年代Region算一个“回收价值”——垃圾量除以回收时间,然后优先挑价值最高的。数量方面,由-XX:G1MixedGCCountTarget(默认8次)控制,一次并发标记周期后,最多干8次Mixed GC。
3.4 Mixed GC的执行过程
并发标记完成后,G1已经知道每个Region的存活对象情况了。接下来,它根据用户设定的最大停顿时间,计算这次能回收多少Region,然后挑出回收价值最高的老年代Region加入CSet。最后用复制算法,把CSet里Region的存活对象挪到别的空闲Region里,清空CSet,搞定。
四、G1完整回收流程
G1的垃圾回收分四个主要阶段,其中并发标记最复杂也最重要。
4.1 阶段一:初始标记(Initial Marking)
这个阶段会STW(Stop The World),但时间极短。它的任务很简单:标记GC Roots能直接关联的对象,同时修改TAMS(Top at Mark Start)指针,记下每个Region当前顶部的位置。通常它和一次年轻代GC一起执行,所以不会产生额外的停顿。
4.2 阶段二:并发标记(Concurrent Marking)
这个阶段和用户线程同时跑,不卡顿。它从GC Roots出发,遍历整个对象图,标记所有存活对象。处理过程中对象引用变了怎么办?G1用了SATB(Snapshot At The Beginning)算法——在标记开始时拍一张对象图的快照。这样就能避免“漏标”问题,代价是会多产生一些浮动垃圾。
4.3 阶段三:最终标记(Final Marking)
又是一个STW阶段,但时间不长。它主要处理并发标记阶段留下的SATB记录,修正引用变化,最后统计每个Region的存活对象数量和垃圾比例。
4.4 阶段四:筛选回收(Live Data Counting and Evacuation)
这是真正干活、也最耗时的阶段,同样是STW。G1根据用户指定的最大停顿时间,选出回收价值最高的Region组成CSet,然后把存活对象复制到别的空闲Region,清空CSet,更新Remembered Set。这一步做完了,垃圾也就处理干净了。
五、G1的关键特性与重要参数
5.1 关键特性
停顿时间预测——G1会根据历史数据估算这次回收要多长时间,从而控制回收集的大小。没有内存碎片——它用复制算法,回收后内存是整齐的。可伸缩性好——不管堆大小还是硬件平台,都能适应。并行与并发结合——多个GC线程一起上,部分阶段还能和用户线程同时跑。
5.2 重要调优参数
| 参数 | 默认值 | 说明 |
|---|---|---|
-XX:MaxGCPauseMillis | 200ms | 最大停顿时间目标,G1会尽量去达成 |
-XX:G1HeapRegionSize | 自动计算 | 每个Region大小,1MB~32MB,2的幂 |
-XX:InitiatingHeapOccupancyPercent | 45% | 触发Mixed GC的老年代占用率阈值 |
-XX:G1MixedGCCountTarget | 8 | 一次并发标记周期后最多执行的Mixed GC次数 |
-XX:G1OldCSetRegionThresholdPercent | 10% | 每次Mixed GC最多回收的老年代Region比例 |
-XX:+UseG1GC | JDK9默认 | 启用G1垃圾收集器 |
六、适用场景与不适用场景
6.1 适用场景(高频考点)
大堆应用(4GB以上)——G1的优势特别明显。对延迟敏感的应用,比如互联网后端服务、交易系统——低延迟是刚需。需要可预测停顿时间——G1能给出相对稳定的停顿,不会突然卡死。还有那些既想要高吞吐量又不想牺牲延迟的——G1在两者之间平衡得不错。
6.2 不适用场景
小堆(低于2GB)——G1的额外开销(Remembered Set等)会比较扎眼。极致吞吐量优先——如果愿意接受较长停顿,Parallel GC可能更合适。巨型对象太多的应用——G1对巨型对象的处理还不够完美,容易产生碎片和Full GC。
七、G1与CMS的对比
| 特性 | G1 | CMS |
|---|---|---|
| 回收算法 | 复制算法(整体)+标记-整理(局部) | 标记-清除 |
| 内存碎片 | 无 | 有 |
| 停顿时间 | 可预测,相对稳定 | 不可预测,可能出现长时间停顿 |
| 堆大小 | 适合大堆(4GB以上) | 适合中小堆 |
| 并发阶段 | 并发标记 | 并发标记、并发清除 |
| Full GC触发 | 老年代不足、巨型对象分配失败 | 老年代不足、Concurrent Mode Failure |
| 吞吐量 | 较高 | 较低 |
八、高频面试考点总结
G1的核心思想是什么?
- 化整为零,堆拆成Region
- 收益优先,优先回收垃圾最多的Region
- 保留分代思想,但逻辑划分
G1的Region分区有什么特点?
- 大小相等,1MB~32MB,2的幂次方
- 分Eden、Survivor、Old、Humongous四种
- 每个Region有对应的Remembered Set
什么是Mixed GC?它和Young GC、Full GC有什么区别?
- Mixed GC回收所有年轻代Region和部分老年代Region
- Young GC只回收年轻代
- Full GC回收整个堆(含方法区)
G1的回收流程分哪几个阶段?哪些STW?
- 初始标记(STW)、并发标记(并发)、最终标记(STW)、筛选回收(STW)
G1如何实现可预测的停顿时间?
- 通过Region分区,把回收范围限制在部分区域
- 计算每个Region的回收价值,优先选价值最高的
- 根据历史数据预测回收时间,控制回收集大小
G1的适用场景是什么?
- 大堆(4GB以上)
- 对延迟敏感的应用
- 需要可预测停顿时间的应用
JVM G1垃圾收集器 面试问答卡片(高频必背版)
模块一:基础概念与核心思想
Q1:G1垃圾收集器的定位和发展历史是什么?
答:JDK 7u4正式引入,JDK 9取代CMS成为默认收集器。它是一款面向服务端应用的低延迟收集器,同时兼顾吞吐量。可以说是HotSpot从“物理分代”向“区域化逻辑分代”转变的里程碑。
Q2:G1的三个核心设计目标是什么?
答:可预测的停顿时间模型(-XX:MaxGCPauseMillis)、高吞吐量(低延迟前提下最大化应用效率)、大堆支持(特别适合4GB以上,避免全堆扫描)。
Q3:G1的核心设计思想是什么?
答:化整为零(堆拆成大小相等的Region)、收益优先(优先回收垃圾最多的Region)、保留分代思想(逻辑划分而非物理隔离)。
模块二:Region分区机制(核心创新)
Q4:G1的Region分区有什么基本特点?
答:整个堆分成2048个大小相等的Region,单个大小1MB~32MB且是2的幂次方。大小由JVM自动计算,也可手动指定。
Q5:G1的Region分为哪几种类型?各自的作用?
| 类型 | 作用 |
|---|---|
| Eden Region | 对象首次分配的区域 |
| Survivor Region | 存放Minor GC后存活的对象 |
| Old Region | 长期存活的对象 |
| Humongous Region | 超过Region大小50%的巨型对象 |
Q6:G1如何处理巨型对象?
答:超过Region大小50%的对象直接分配在老年代连续Humongous Region中,只会在Mixed GC或Full GC里回收。需要连续空间,容易产生碎片;JDK 8u40后回收效率有改善。
Q7:Remembered Set的作用是什么?
答:解决跨Region引用,避免全堆扫描。每个Region都有自己的记忆集,当A Region引用B Region时,就在B的Remembered Set里记录。回收时只扫对应Set,空间代价约占堆的1%~5%。
模块三:Mixed GC机制(G1独有)
Q8:什么是Mixed GC?为什么需要它?
答:Mixed GC同时回收所有年轻代Region和部分垃圾最多的老年代Region。传统分代收集器只能单独回收一代,G1通过Mixed GC在指定停顿时间内回收更多垃圾,避免Full GC。
Q9:Mixed GC的触发条件是什么?
答:老年代占用率达到-XX:InitiatingHeapOccupancyPercent(默认45%)。
Q10:什么是回收集(CSet)?
答:CSet是Mixed GC中要回收的Region集合,包括所有年轻代Region和部分回收价值最高的老年代Region。选择算法是计算每个Region的“垃圾量/回收时间”,优先选价值高的。数量由-XX:G1MixedGCCountTarget(默认8)控制。
模块四:完整回收流程
Q11:G1的回收流程分哪几个阶段?哪些STW?
答:初始标记(STW,极短)、并发标记(并发无停顿)、最终标记(STW,较短)、筛选回收(STW,主要停顿阶段)。
Q12:初始标记阶段做了什么?
答:STW,通常伴随Young GC执行,任务:标记GC Roots直接关联的对象,修改TAMS指针。没有额外停顿。
Q13:并发标记阶段做了什么?SATB算法的作用?
答:与用户线程并发,从GC Roots遍历对象图标记存活对象。SATB在标记开始时保存对象图快照,解决漏标问题,代价是产生少量浮动垃圾。
Q14:最终标记阶段做了什么?
答:STW,处理SATB记录,修正引用变化,统计每个Region的存活对象和垃圾比例。
Q15:筛选回收阶段做了什么?
答:STW,根据最大停顿时间选择价值最高的Region组成CSet,复制存活对象到空闲Region,清空CSet,更新Remembered Set。
模块五:关键特性与调优参数
Q16:G1如何实现可预测的停顿时间?
答:通过Region分区限制回收范围,计算每个Region的回收价值,根据历史数据预测回收时间,精确控制回收集大小。
Q17:G1最重要的5个调优参数是什么?
| 参数 | 默认值 | 作用 |
|---|---|---|
-XX:MaxGCPauseMillis | 200ms | 最大停顿时间目标 |
-XX:G1HeapRegionSize | 自动计算 | Region大小,1MB~32MB,2的幂 |
-XX:InitiatingHeapOccupancyPercent | 45% | 触发Mixed GC的老年代阈值 |
-XX:G1MixedGCCountTarget | 8 | 一次并发标记后最多Mixed GC次数 |
-XX:+UseG1GC | JDK9默认 | 启用G1 |
模块六:适用场景与对比
Q18:G1的适用场景是什么?
答:大堆(4GB以上)、对延迟敏感(互联网后端、交易系统)、需要可预测停顿、吞吐量与延迟兼顾。
Q19:G1的不适用场景?
答:小堆(2GB以下)G1开销明显;极致吞吐量优先(Parallel GC更好);巨型对象过多(碎片和Full GC风险)。
Q20:G1和CMS的核心区别?
| 特性 | G1 | CMS |
|---|---|---|
| 回收算法 | 复制+标记-整理 | 标记-清除 |
| 内存碎片 | 无 | 有 |
| 停顿时间 | 可预测,稳定 | 不可预测 |
| 堆大小 | 适合大堆 | 适合中小堆 |
| 回收范围 | 部分Region | 整个老年代 |
| Full GC触发 | 老年代不足、巨型对象失败 | 老年代不足、Concurrent Mode Failure |
| 吞吐量 | 较高 | 较低 |
JVM G1垃圾收集器 面试问答卡片(进阶版)
模块七:G1常见问题排查(生产高频)
Q21:G1频繁触发Full GC的常见原因?如何排查?
现象:GC日志中频繁出现Full GC (Allocation Failure)或Full GC (Ergonomics),应用卡顿。
常见原因及排查:
- 巨型对象过多:查看
Humongous Allocation次数,增大-XX:G1HeapRegionSize或优化代码。 - 老年代增长过快:查看占用率变化,降低
InitiatingHeapOccupancyPercent提前触发Mixed GC。 - Mixed GC回收效率不足:查看每次回收的老年代Region数量,增加
G1MixedGCCountTarget。 - 内存泄漏:用jmap、MAT分析堆转储,修复代码。
Q22:G1 Mixed GC耗时过长的原因及优化?
现象:STW时间超过MaxGCPauseMillis。
原因及优化:
- CSet过大:降低
G1OldCSetRegionThresholdPercent。 - 存活对象过多:调整
MaxTenuringThreshold让对象尽快晋升。 - Remembered Set扫描耗时:减少跨代引用,增大
G1RSetSparseRegionEntries。 - GC线程数不足:增加
ParallelGCThreads。
Q23:G1中巨型对象会导致哪些问题?
答:内存碎片、提前触发Full GC、回收效率低(只在Mixed/Full GC处理)、占用老年代空间。解决方案:增大Region大小、拆分对象、避免频繁创建巨型对象。
Q24:G1停顿时间超标如何排查?
排查步骤:看GC日志确定是哪个阶段超时。筛选回收阶段检查CSet大小、存活比例、Remembered Set耗时;最终标记阶段检查SATB记录量;初始标记阶段关联Young GC。通用优化:适当降低停顿目标、增加线程数、调整老年代阈值。
Q25:G1 Remembered Set占用内存过高怎么办?
现象:JVM占用远超堆内存,jstat显示RSet高。原因:跨Region引用多。方案:增大Region大小、调整G1RSetUpdatingPauseTimePercent、优化代码减少跨代引用、适当降低堆大小。
Q26:G1并发标记周期过长会导致什么问题?
答:老年代在标记期间继续增长,可能提前触发Full GC;浮动垃圾增多。原因:堆大对象多、并发线程不足、CPU被用户线程抢占。方案:增加ConcGCThreads、降低InitiatingHeapOccupancyPercent、优化应用CPU占用。
模块八:生产环境调优案例(实战高频)
Q27:16GB堆内存的互联网后端服务基础调优
业务:电商订单系统,日均100万单,峰值QPS 5000,堆16GB。问题:平均停顿300ms,峰值500ms,偶尔Full GC。
调优参数:
-Xms16g -Xmx16g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=16m -XX:InitiatingHeapOccupancyPercent=35 -XX:G1MixedGCCountTarget=10 -XX:ParallelGCThreads=8 -XX:ConcGCThreads=2
效果:平均停顿降至120ms,峰值不超过200ms,Full GC消失。
Q28:金融交易系统低延迟调优
业务:证券交易,要求单次延迟<10ms,堆8GB。问题:GC停顿波动大,偶现200ms以上。
调优参数:
-Xms8g -Xmx8g -XX:+UseG1GC -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=8m -XX:InitiatingHeapOccupancyPercent=30 -XX:G1OldCSetRegionThresholdPercent=5 -XX:G1MixedGCCountTarget=16 -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=30 -XX:G1MaxNewSizePercent=40
效果:99.9%的GC停顿<50ms。
Q29:巨型对象过多导致频繁Full GC的调优
业务:大数据处理,频繁处理10~20MB字节数组,堆32GB。问题:每10分钟一次Full GC,日志大量Humongous Allocation。
调优前:-Xms32g -Xmx32g -XX:+UseG1GC -XX:G1HeapRegionSize=8m(10MB对象成巨型)。
调优后:-Xms32g -Xmx32g -XX:+UseG1GC -XX:G1HeapRegionSize=32m -XX:InitiatingHeapOccupancyPercent=40。
效果:Full GC降至每天1次,吞吐量提升30%。
Q30:高吞吐量批处理系统G1调优
业务:离线批处理,每天TB级数据,追求高吞吐。问题:Parallel GC时Full GC停顿超10秒。
调优参数:
-Xms64g -Xmx64g -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:G1HeapRegionSize=32m -XX:InitiatingHeapOccupancyPercent=50 -XX:G1MixedGCCountTarget=4 -XX:ParallelGCThreads=16 -XX:ConcGCThreads=4
效果:单次GC停顿<500ms,整体吞吐量提升20%。
模块九:G1调优最佳实践
Q31:G1调优的基本原则是什么?
答:不要过度调优,G1自适应机制已经很完善;优先调整堆大小和Region大小;不要手动设置年轻代大小;所有调优基于GC日志;小步迭代,一次只调一个参数。
Q32:G1调优的一般步骤?
答:开启GC日志(-Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps);用GCEasy、GCViewer分析;确定瓶颈(停顿、吞吐、Full GC);针对性调优;测试环境验证后推广到生产。
