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+ 小伙伴加入学习,欢迎点击围观

面试考察点

  1. GC 分类认知:面试官想知道你是否清楚不同 GC 类型各自的回收范围和触发时机,而不是笼统地回答 "内存不够了就 GC"。

  2. JVM 调优潜力:了解触发条件是 GC 调优的前提。如果你连什么情况下会触发 Full GC 都说不清,面试官很难相信你能处理线上的 GC 问题。

  3. 细节深度:像空间分配担保、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()
算法 复制算法 取决于收集器(标记-清除/标记-整理)
停顿时间 几毫秒~几十毫秒 几百毫秒~几秒甚至更长
频率 非常频繁(可能几秒一次) 应尽量少(理想情况几小时甚至不发生)
影响 用户基本无感知 可能导致服务超时、响应抖动

面试高频追问

  1. 追问:线上服务频繁 Full GC 怎么排查?

    第一步看 GC 日志,确认 Full GC 的频率和耗时;第二步用 jstat -gcutil 观察各区域使用率;第三步 jmap -dump:format=b,file=heap.hprof 导出堆快照,用 MAT 或 VisualVM 分析,找出占用空间最大的对象和可能的内存泄漏点。

  2. 追问:如何避免 Full GC?

    几个方向:调大年轻代让 Young GC 更 "能打"、确保 Survivor 区够大避免对象过早晋升、排查大对象分配、选择低停顿收集器(G1/ZGC)、禁用显式 System.gc()、排查内存泄漏。

  3. 追问: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 的触发条件分条讲清楚,再配上实际的排查和调优思路,这块分数稳拿。