📌 PDF :大白话说Java面试题 --- 02-JVM篇
第26题:介绍一下 G1 垃圾收集器
📚 回答:
- 核心考点 :
G1(Garbage First)是JDK 9+的默认垃圾收集器,设计目标是替代CMS ,在大堆内存(≥4GB)场景下实现可预测的停顿时间 ,同时避免内存碎片。面试需掌握其Region布局、停顿模型、与CMS的本质区别。
1. G1 的核心设计思想
| 特性 | 说明 |
|---|---|
| Region化堆内存 | 将堆划分为多个大小相等的Region(1~32MB,默认2048个),每个Region可动态扮演Eden、Survivor、Old、Humongous(大对象)角色 |
| 停顿预测模型 | 基于历史数据(每个Region的回收耗时)选择**高收益Region集合(CSet)**回收,使停顿时间可控 |
| 增量式回收 | 每次只回收部分Region,而非全堆,避免长时间STW |
| 全局并发标记 | 并发标记存活对象,最终通过筛选回收阶段 复制存活对象到新Region,彻底消除碎片 |
2. G1 的内存布局(关键区别)
与传统分代GC对比:
- 传统(Parallel/CMS):物理连续分区(Eden、S0、S1、Old)
- G1:逻辑分代 + 物理Region隔离,某Region可以从未分代动态切换
大对象(Humongous Region):
- 对象大小 > Region大小的50%
- 存放在连续的Humongous Region中(H区)
- 回收时效率低,需避免过多大对象
3. G1 的工作阶段(面试详版)
| 阶段 | 类型 | 主要动作 | 是否STW | 备注 |
|---|---|---|---|---|
| 初始标记 | Young GC期间附属 | 标记GC Roots直接可达对象 | 是 | 借用Young GC停顿,几乎无额外开销 |
| 并发标记 | 并发 | 从GC Roots遍历对象图,计算每个Region存活对象 | 否 | 标记过程中应用线程继续运行 |
| 最终标记 | 并发+短暂STW | 处理SATB(Snapshot-At-The-Beginning)漏标对象 | 是 | 修正并发标记期间的引用变化 |
| 筛选回收 | 混合模式 | 选择回收价值最高的Region(CSet),复制存活对象 | 是 | 可并行,停顿时间由MaxGCPauseMillis控制 |
| Full GC | 后备 | 单线程Serial Old回收全堆 | 是(很长) | 当并发回收赶不上对象分配时触发 |
关键点:
- SATB(Snapshot-At-The-Beginning):G1通过写屏障维护并发标记开始时对象图的快照,防止漏标。
- Mixed GC:筛选回收阶段会同时回收年轻代+部分老年代Region。
4. G1 停顿时间模型(面试核心)
参数:
bash
-XX:MaxGCPauseMillis=200 # 目标200ms(默认值),不是硬性保证,G1会尽量逼近
-XX:GCPauseIntervalMillis # 停顿间隔,默认未设置
实现机制:
- G1会记录每个Region的历史回收耗时(平均、标准差等)
- 年轻代大小动态调整:若历史停顿时间 > 目标,缩小年轻代;否则扩大
- 筛选回收时,按垃圾/回收耗时比对Region排序,从高到低选入CSet,直到预估总时间接近目标值
误区 :设置MaxGCPauseMillis太小(如10ms)会导致年轻代极小,Young GC频繁,吞吐下降。
5. G1 vs CMS 深度对比
| 对比维度 | G1 | CMS |
|---|---|---|
| 堆布局 | Region化,逻辑分代 | 物理连续分代(Eden+S0+S1+Old) |
| 碎片问题 | 无碎片(复制整理) | 严重碎片(标记-清除) |
| 停顿时间 | 可预测,可控制(默认200ms) | 不可控,随老年代碎片恶化 |
| Full GC触发 | 并发回收赶不上分配时 | 碎片导致晋升失败/并发模式失败 |
| CPU敏感度 | 中等 | 高(并发标记占CPU) |
| 内存占用 | 每个Region有Remembered Set(RS)额外占用5%~10%堆内存 | 较低 |
| 大对象处理 | Humongous Region(效率低) | 直接进老年代(易导致碎片) |
| JDK版本 | JDK 7u4引入,JDK 9+默认 | JDK 9废弃,JDK 14移除 |
6. G1 常用调优参数
| 参数 | 含义 | 建议值 | 风险提示 |
|---|---|---|---|
-XX:MaxGCPauseMillis |
目标停顿时间 | 100~300ms | 太小导致频繁GC |
-XX:G1HeapRegionSize |
Region大小 | 自动(1/2/4/8/16/32MB) | 手动设需是2的幂 |
-XX:InitiatingHeapOccupancyPercent |
触发并发标记的老年代占用比例 | 默认45% | 过低频繁并发标记,过高易Full GC |
-XX:G1NewSizePercent |
年轻代最小值(占堆百分比) | 默认5% | 太小导致频繁Young GC |
-XX:G1MaxNewSizePercent |
年轻代最大值 | 默认60% | 太大可能导致老年代回收赶不上 |
-XX:ParallelGCThreads |
并行GC线程数 | 默认CPU数5/8 | 容器内需设置≤CPU核数 |
-XX:ConcGCThreads |
并发GC线程数 | 默认ParallelGCThreads的1/4 | 并发标记阶段占CPU |
7. G1 典型问题与排查
问题1:Mixed GC后仍有大量老年代内存未回收,持续Full GC
- 根因:
InitiatingHeapOccupancyPercent过大导致并发标记触发晚,老年代堆积过多 - 解决:调小至35~40%,或增大
MaxGCPauseMillis
问题2:Humongous对象分配导致频繁Full GC
- 根因:大对象(>Region 50%)分配后,回收代价高
- 解决:应用层拆分大对象,或增大Region(
-XX:G1HeapRegionSize=32m)
问题3:RS(Remembered Set)占用内存过高
- 根因:Region间引用复杂(跨区引用多)
- 解决:正常现象,限制G1堆内存<16GB(JDK 8之前),或升级JDK(优化了RS压缩)
8. 面试官追问示例
Q1:G1 一定比 CMS 好吗?
A:不是。CMS在堆内存<4GB、停顿不敏感的场景仍然高效。G1的RS维护占用额外内存(~10%),小堆上得不偿失。
Q2:G1 的 Mixed GC 频率如何控制?
A:由InitiatingHeapOccupancyPercent控制:当老年代占用超过此阈值时,触发并发标记,标记完成后触发Mixed GC。Mixed GC会持续多次,每次回收部分老年代Region。
Q3:G1 能完全避免 Full GC 吗?
A:不能。典型情况:
- 并发模式失败(老年代未标记完就满了)
- 晋升失败(Survivor/老年代Region无空间放晋升对象)
- 大对象分配(找不到连续Humongous Region)
Q4:G1 中 Young GC 也是 STW 的,为什么说 G1 低延迟?
A:因为Young GC虽然STW,但G1通过动态调整年轻代大小,让每次Young GC停顿时间可控 (接近MaxGCPauseMillis)。而Parallel GC年轻代固定,停顿可能更长。
Q5:G1 适用于多大堆内存?
A:官方推荐6GB~64GB。大于64GB推荐ZGC(停顿<10ms),小于6GB可用Parallel或G1(但RS内存开销明显)。
💡 面试官想要的满分总结:
"G1是Region化、增量式、可预测停顿的垃圾收集器,通过复制整理消除碎片。核心机制包括:堆分为等大小Region、Remembered Set避免全局扫描、SATB保证并发标记正确性、根据历史数据选择高收益Region回收。
适用堆4~64GB,调优核心是
MaxGCPauseMillis(默认200ms)和InitiatingHeapOccupancyPercent(默认45%)。与CMS相比,G1无碎片、停顿可控,但内存占用略高。大堆场景下,G1优于CMS;超大堆(>64GB)应选ZGC/Shenandoah。"
觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯