YoungGC 和 FullGC 的触发条件是什么?
一则或许对你有用的小广告
欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书
《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于
Spring AI + Spring Boot 3.x + JDK 21...,查看介绍《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...,查看介绍;演示链接:http://116.62.199.48:7070/《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/
新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍
截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观
面试考察点
-
GC 分类认知:面试官想知道你是否清楚不同 GC 类型各自的回收范围和触发时机,而不是笼统地回答 "内存不够了就 GC"。
-
JVM 调优潜力:了解触发条件是 GC 调优的前提。如果你连什么情况下会触发 Full GC 都说不清,面试官很难相信你能处理线上的 GC 问题。
-
细节深度:像空间分配担保、
System.gc()的影响、元空间触发 Full GC 这些细节,区分度很高。
核心答案
直接上结论,用一张表先把全貌拉齐:
| GC 类型 | 回收区域 | 核心触发条件 | 频率 | STW 影响 |
|---|---|---|---|---|
| Young GC(Minor GC) | Eden + Survivor | Eden 区空间不足 | 非常频繁 | 较小 |
| Full GC | 整个堆 + 元空间 | 老年代不足、元空间不足、System.gc() 等 |
应尽量少 | 很大 |
Young GC 触发条件就一个:Eden 区放不下新对象了。
Full GC 触发条件比较多,需要逐条说清楚。
深度解析
一、Young GC 的触发条件
很简单,就一个——
当新对象分配到 Eden 区时,Eden 区空间不够了,就触发 Young GC。
这里有个细节容易搞错:Young GC 不是只回收 Eden 区,而是回收整个年轻代(Eden + S0 + S1)。只不过大部分时候 Survivor 区里的对象很少,所以给人的感觉是 "Eden 满了就 GC"。
还有一点,Survivor 区满了 不会 单独触发 GC。Survivor 区只是 Young GC 过程中存活对象的中转站,它本身没有独立的 GC 触发机制。
// 假设 Eden 区大小为 20MB
byte[] arr1 = new byte[10 * 1024 * 1024]; // Eden 还剩 10MB
byte[] arr2 = new byte[10 * 1024 * 1024]; // Eden 还剩 0MB
byte[] arr3 = new byte[1 * 1024 * 1024]; // Eden 放不下了 → 触发 Young GC
上面这段伪代码就是最典型的 Young GC 触发场景。arr3 要分配 1MB,Eden 已经满了,GC 来了。
二、Full GC 的触发条件
这块是面试重点,至少得答出 3~4 条。我按 常见程度 排序:
1. 老年代空间不足
这是最常见的 Full GC 触发原因。老年代空间不够,通常由以下几种情况导致:
- 对象在年轻代经历了多次 GC 仍然存活,年龄达到阈值晋升到老年代
- 大对象直接分配到老年代(超过
-XX:PretenureSizeThreshold) - 内存泄漏,导致对象无法被回收,老年代越来越满
2. 空间分配担保失败
这个稍微绕一点。在 Young GC 之前,JVM 会做一次 "风险评估":
上图的逻辑拆解如下:
- Young GC 前,JVM 先检查老年代剩余空间够不够放年轻代可能晋升过来的所有对象
- 如果够,直接 GC;如果不够,再看是否允许 "冒险"
- 允许冒险就赌一把(因为实际晋升的对象通常远少于年轻代总对象),赌输了就 Full GC
- 不允许冒险就老老实实先 Full GC
注意:JDK 6 Update 24 之后,这个规则简化了。只要老年代连续空间大于年轻代现有对象总大小 或者 大于历次晋升对象的平均大小,就允许 Young GC,否则直接 Full GC。-XX:-HandlePromotionFailure 参数在这个版本之后不再生效。
3. System.gc() 被显式调用
// 代码里显式调用
System.gc();
// 或者
Runtime.getRuntime().gc();
这方法名就说明了——它是在 "建议" JVM 做一次 Full GC。虽然 JVM 不一定立即执行,但大多数情况下它会响应,而且往往会触发一次完整的 Full GC。
生产环境中,这个调用经常是 "坑",因为有些第三方库或 NIO 的 DirectByteBuffer 回收时会调用 System.gc()。阿里开发手册也明确说了不要手动调用。如果你确实想禁掉,可以用:
-XX:+DisableExplicitGC
加了之后 System.gc() 就变成空操作了。
4. 元空间(Metaspace)不足
JDK 8 把方法区的实现从永久代(PermGen)改成了元空间(Metaspace),元空间使用的是本地内存,不在堆里。但元空间满了同样会触发 Full GC:
# 设置元空间最大值(不设的话默认只受限于物理内存)
-XX:MaxMetaspaceSize=256m
当加载的类太多(比如用了大量动态代理、Groovy 脚本、CGLIB 等),元空间就会不够用,触发 Full GC 来回收无用的类信息。
5. Young GC 后存活对象超过老年代剩余空间
Young GC 结束后,要晋升到老年代的对象总大小超过了老年代当前可用空间。这种情况等价于 "老年代空间不足",会接着触发 Full GC。
三、Young GC 与 Full GC 对比一览
| 维度 | Young GC | Full GC |
|---|---|---|
| 回收区域 | 年轻代(Eden + S0 + S1) | 整个堆 + 元空间 |
| 触发条件 | Eden 区空间不足 | 老年代不足、元空间不足、担保失败、System.gc() 等 |
| 算法 | 复制算法 | 取决于收集器(标记-清除/标记-整理) |
| 停顿时间 | 几毫秒~几十毫秒 | 几百毫秒~几秒甚至更长 |
| 频率 | 非常频繁(可能几秒一次) | 应尽量少(理想情况几小时甚至不发生) |
| 影响 | 用户基本无感知 | 可能导致服务超时、响应抖动 |
面试高频追问
-
追问:线上服务频繁 Full GC 怎么排查?
第一步看 GC 日志,确认 Full GC 的频率和耗时;第二步用
jstat -gcutil观察各区域使用率;第三步jmap -dump:format=b,file=heap.hprof导出堆快照,用 MAT 或 VisualVM 分析,找出占用空间最大的对象和可能的内存泄漏点。 -
追问:如何避免 Full GC?
几个方向:调大年轻代让 Young GC 更 "能打"、确保 Survivor 区够大避免对象过早晋升、排查大对象分配、选择低停顿收集器(G1/ZGC)、禁用显式
System.gc()、排查内存泄漏。 -
追问:CMS 收集器什么时候会触发 Full GC?
CMS 正常情况下只回收老年代(Major GC),但在并发模式失败(Concurrent Mode Failure,老年代在 CMS 并发清理期间空间不够了)时会退化成 Serial Old 做 Full GC,这个退化非常可怕,停顿时间会很长。
常见面试变体
- "什么情况下会触发 Full GC?"
- "如何减少 Full GC 的频率?"
- "Young GC 和 Full GC 的区别?"
- "
System.gc()会立即触发 GC 吗?"
记忆口诀
Young GC:Eden 满了就 GC,一个条件就够了。
Full GC:老年代满、元空间满、担保失败、显式调用——"老元担调" 四字诀。
总结
Young GC 的触发条件很单纯,就是 Eden 区空间不足。Full GC 的触发条件比较多,核心是老年代空间不足,其次是元空间不足、空间分配担保失败、System.gc() 显式调用等。面试中把 Full GC 的触发条件分条讲清楚,再配上实际的排查和调优思路,这块分数稳拿。
