小米来咯!这次咱们聊聊一个 Java 面试中出场率极高的"硬菜"------G1 垃圾回收器(Garbage-First GC) 。作为一名在互联网大厂摸爬滚打了多年的 Java 工程师,我可是亲身见证了从 CMS 到 G1 的演进,也踩过不少 GC 的坑。今天我们就来系统、轻松、故事化地把 G1 GC 讲清楚,既能帮你面试拿下高分,也能让你写代码更安心。
一个真实的"救火"故事:我和 G1 GC 的第一次亲密接触
那是三年前的一个深夜,线上交易系统突然响应变慢,甚至偶尔还有超时报警。我刚洗完澡准备睡觉,领导电话就打了过来:"小米,赶紧上来看一下,线上服务抖得厉害,GC 时间飙上去了!"
我一登录,jstat 一查,果不其然:Full GC 十几秒一次,每次暂停 5~6 秒,简直是大型事故现场。我们的老服务还在用 CMS,那会儿堆内存给到了 20G,GC 时老年代碎片太多,导致频繁 Full GC,系统抖得不行。
我当时果断切换到了 G1 GC,经过一番配置调优,系统瞬间稳定了。从那以后,我就成了 G1 的死忠粉。
G1 GC 是什么?它想解决什么问题?
我们先来看看 G1 是为了解决谁的问题而生的。
在 G1 出现之前,老牌的 CMS GC 用得很多,但 CMS 有三个致命缺陷:
- 无法整理内存碎片:老年代回收后留下的碎片可能导致Promotion Failed。
- 并发阶段复杂:容易触发Concurrent Mode Failure,最终退化成 Full GC。
- GC 时间不可控:无法精准控制每次停顿的时间。
而 G1 GC 就是为了解决这些问题而诞生的。
它的名字就很有意思:Garbage-First,意思是"优先回收垃圾多的地方"。听起来是不是有点像那个只抓重点抄作业的聪明同学?
G1 GC 的内存布局:Region 来了!
传统的堆内存分为:年轻代(Young)+ 老年代(Old) ,其中年轻代又分 Eden 和 Survivor,但 G1 的结构有了重大改变。
G1 把整个堆划分成了若干个大小相同的 Region(比如 1~32MB 一个块),每个 Region 可以承担不同的角色:
- Eden Region:存放新对象
- Survivor Region:幸存者对象
- Old Region:晋升的老对象
- Humongous Region:专门放"大对象"(超过 50% Region 大小)
这种设计有啥好处?灵活!不再像 CMS 一样"前一块儿是年轻代,后一块儿是老年代",而是可以动态调整内存使用。
G1 GC 的核心流程:四步走
G1 的 GC 类型主要有四种:Young GC、Mixed GC、Full GC、Concurrent Cycle。核心流程分为以下四步:
- Young GC(年轻代收集)
和其他 GC 类似,G1 每次 Minor GC 时只收 Eden 区域,把幸存对象复制到 Survivor 或晋升到 Old。暂停时间短,效率高。
- Concurrent Marking(并发标记阶段)
当老年代使用率达到一定阈值(比如 45%)时,G1 会启动一次"并发标记":
- 初始标记:STW(Stop The World),标记 GC Roots 可达对象
- 并发标记:不 STW,扫描整个堆,识别出存活对象
- 最终标记:STW,处理 RSet 更新
- 筛选回收:确定要回收哪些 Region,准备 Mixed GC
- Mixed GC(混合收集)
这是 G1 最"聪明"的一招!不像 CMS 只能收年轻代,G1 会同时收回年轻代和部分老年代中的高回收收益 Region。
通过精细控制 Region 的选择,G1 可以让 GC 更加高效,而且 暂停时间可控(这是面试高频点!)。
- Full GC(最后的杀招)
当 G1 实在没招了,比如 Humongous 对象太多,GC 压力过大,它会触发 Full GC。这个过程依然是 STW,而且比 CMS 快,但我们要尽量避免它。
G1 GC 的最大亮点:暂停时间可控
这是 G1 最吸引面试官的点:可以指定最大停顿时间(Pause Time Goal) !
比如:
-XX:MaxGCPauseMillis=200
意思是:我希望每次 GC 暂停不超过 200 毫秒。G1 会努力达成这个目标(但不能百分百保证)。
它通过预估 GC 成本、回收收益,来"聪明地"选择回收哪些 Region,从而在性能和响应之间找平衡。
那些你可能忽略的 G1 黑科技
1、Remembered Set(RSet)
G1 不再像 CMS 那样全扫描,而是维护一个每个 Region 的"指针引用表",可以只扫描必要的区域,减少开销。
2、Card Table + SATB 写屏障
为了支持并发标记,G1 引入了快照式读屏障(Snapshot-At-The-Beginning) ,配合 Card Table 实现对引用的精确跟踪。
3、Humongous Object 分配优化
G1 会优先处理大对象,但如果大对象太多,容易触发 Full GC。可以通过调整 -XX:G1HeapRegionSize 和 -XX:G1HeapWastePercent 来优化。
常见 G1 GC 面试问题梳理(建议收藏)
- G1 和 CMS 的本质区别是什么?
- G1 如何实现暂停时间可控?
- G1 的内存结构为什么要划分为 Region?
- G1 如何解决 CMS 的内存碎片问题?
- G1 为什么 Mixed GC 是重点?
- 什么是 Remembered Set?SATB 是干嘛的?
- G1 是不是就不需要 Full GC?
- G1 的调优参数有哪些关键的?
总结:为什么我推荐使用 G1 GC?
G1 不是银弹,但它适合这些场景:
- 堆内存大于 4GB 的应用
- 对响应时间有要求(暂停时间敏感)
- 容易出现 CMS 碎片、Concurrent Mode Failure 的服务
从 Java 9 开始,G1 就成了默认 GC,说明它的稳定性已经得到了社区认可。
彩蛋时间:一条最能让你面试加分的 G1 表述
"G1 是一种面向服务端的大内存垃圾回收器,它通过将堆划分为 Region,实现并发标记+分代回收,支持用户设定最大停顿时间,从而在吞吐量与响应时间之间取得平衡。"
看到没?这一段背下来,保证面试官心里暗暗点赞!
END
好了,今天的分享就到这啦,G1 GC 虽然底层原理不少,但只要我们理清"Region + Mixed GC + Pause Control"这三大核心,就已经掌握了它的 80%!
你还想听我讲哪个 GC?ZGC?Shenandoah?留言区告诉我呀~
我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号"软件求生",获取更多技术干货!