Minor GC、Major GC、Full GC 有什么区别?
简化版
这些名称描述的是不同的回收范围,但并非 JVM 规范中的统一术语。通常 **Young GC(常被叫 Minor GC)**回收年轻代;Major GC含义最不统一,可能指老年代回收,也可能被当作 Full GC;Full GC通常表示一次全堆级回收,并可能连带类卸载。看线上日志时,应以收集器名称、回收阶段和实际回收区域为准,不能只看术语猜范围。
详细版
分代收集器利用“大多数对象朝生夕灭”的规律,把新对象放入年轻代并频繁回收,把长期存活对象晋升到老年代。
- Young GC:主要回收年轻代,通常会暂停应用线程;存活对象可能复制到 Survivor 或晋升到老年代。
- Old GC / Major GC:可能表示老年代回收,但
Major GC没有跨收集器统一定义。 - Mixed GC:G1 完成并发标记后,一次回收年轻代 Region 和一部分收益较高的老年代 Region。
- Full GC:通常是全堆级兜底回收,暂停往往更重,但具体阶段和范围仍取决于收集器与 JDK 版本。
频繁 Full GC 是重要告警信号,但不能直接等同于内存泄漏;堆过小、晋升失败、元空间压力、显式 GC 或收集器退化都可能触发它。
完整版教学
一、先分清“规范概念”和“日志术语”
Java 虚拟机规范定义运行时数据区和对象语义,并没有要求所有实现都使用年轻代、老年代,也没有统一规定 Minor、Major、Full GC 的名称。
这些词主要来自具体 JVM 实现、收集器和诊断日志。因此面试中更严谨的答法是:先给出常见含义,再补一句“最终以所用收集器和 GC 日志为准”。
| 术语 | 常见含义 | 必须注意的边界 |
|---|---|---|
| Young / Minor GC | 回收年轻代 | 通常 STW,也可能伴随对象晋升 |
| Old / Major GC | 老年代相关回收 | Major 的含义不统一 |
| Mixed GC | 年轻代加部分老年代 | 典型见于 G1 |
| Full GC | 全堆级或收集器的兜底回收 | 是否处理类卸载、采用何算法取决于实现 |
因此,“Major GC 一定只收老年代”与“Full GC 一定逐字等于年轻代加老年代加元空间”都说得过满。
二、Young GC 为什么通常更频繁
分代回收基于弱分代假说:大多数新对象很快失去引用,少量对象会长期存活。
年轻代回收时,收集器只需保留少量存活对象。例如一次 Young GC 前 Eden 使用 800 MB,回收后只剩 40 MB 存活,则存活率为:
存活率 = 40 MB / 800 MB = 5%
回收空间约为 760 MB
对于复制式年轻代回收,成本更接近“扫描和复制存活对象”,而不是简单正比于垃圾总量。年轻代存活率低时,这种设计很划算。
但“Young GC 一定很快”也不是定律。根集合大、跨代引用多、对象存活率高或机器发生调度抖动时,Young GC 暂停也可能明显升高。
三、Young GC 会做哪些事
一次典型的分代 Young GC 会经历以下工作:
- 在安全点暂停需要暂停的应用线程。
- 从 GC Roots 和老年代到年轻代的跨代引用出发标记存活对象。
- 将存活对象复制到 Survivor,或按年龄、空间等条件晋升。
- 更新对象引用、年龄和区域角色。
- 释放本次回收区域并恢复应用线程。
老年代对象并不会因为 Young GC 就全部被遍历一遍。分代收集器通常借助卡表、记忆集等结构记录“老年代指向年轻代”的引用,否则年轻代回收会失去小范围回收的意义。
Young GC 的“只收年轻代”是指主要回收范围,不代表它完全不需要读取年轻代之外的引用信息。
四、G1 为什么还有 Mixed GC
G1 把堆划分为多个 Region,逻辑上仍区分年轻代和老年代,但它们不要求物理连续。
G1 的并发标记识别老年代 Region 的存活情况后,会选择回收收益较高的老年代 Region。后续 Mixed GC 同时处理:
- 当前年轻代 Region;
- 一部分被选中的老年代 Region;
- 相关记忆集和引用更新。
假设候选老年代 Region 各为 16 MB,其中 A 只有 2 MB 存活、B 有 14 MB 存活,那么回收 A 理论上可释放约 14 MB,收益通常高于只能释放约 2 MB 的 B。实际选择还受暂停目标、记忆集成本等因素影响。
Mixed GC 不是 Full GC:它只选择部分老年代 Region,目标是在可控暂停内逐步回收老年代。
五、Full GC 为什么通常更重
Full GC 往往需要处理更大的对象范围,并可能使用压缩整理或收集器的退化路径,因此暂停通常显著高于常规 Young GC。
常见诱因包括:
- 老年代或整个堆无法满足分配、晋升;
- 元空间达到压力阈值,需要尝试类卸载;
System.gc()触发显式 GC,具体行为受参数和收集器影响;- 并发收集来不及完成,退化为 STW 回收;
- 堆碎片或超大对象分配找不到合适空间。
System.gc() 只是向 JVM 提出建议,并非语言层面的强制命令。-XX:+DisableExplicitGC 也不能替代定位问题,而且对依赖显式 GC 处理堆外引用的旧式程序可能有副作用。
六、怎样用日志判断,而不是凭名字猜
诊断时至少同时看四组信息:
| 观察项 | 示例问题 | 能帮助判断什么 |
|---|---|---|
| 原因 | Allocation Failure、Metadata GC Threshold | 为什么启动回收 |
| 阶段/类型 | Young、Mixed、Full、Concurrent | 实际执行了哪条路径 |
| 回收前后容量 | 2G->900M(4G) | 释放量和长期占用趋势 |
| 暂停与并发耗时 | 20 ms、1.2 s | 延迟影响和瓶颈阶段 |
例如某次日志显示 3.6G->3.4G(4G),回收 200 MB 后仍占 3.4 GB。单次数据不能证明泄漏,但若多轮 Full GC 后最低占用持续上升,就应结合堆转储检查长期可达对象。
吞吐量可用下面的近似式理解:
GC 吞吐量 = 应用运行时间 /(应用运行时间 + GC 时间)
10 秒窗口内应用运行 9.5 秒、GC 累计 0.5 秒,吞吐量约为 95%。延迟敏感系统还必须看暂停的 P95、P99,而不能只看平均值。
七、常见误区与追问
- 误区:Minor GC 一定不会暂停业务线程。 常见分代收集器的 Young GC 仍有 STW 阶段,只是范围通常较小。
- 误区:Major GC 在所有 JVM 中都只回收老年代。 这个术语没有统一边界,必须结合收集器与日志解释。
- 误区:Full GC 频繁就一定是内存泄漏。 堆配置、晋升压力、元空间、显式 GC 和收集器退化都可能造成同样现象。
- 追问:Mixed GC 和 Full GC 的关键区别是什么? Mixed GC 只选择部分老年代 Region 加年轻代回收,Full GC 通常走全堆级兜底路径。
- 追问:为什么 Young GC 需要记忆集? 它要找到老年代指向年轻代的引用,又不能每次完整扫描老年代。
- 追问:一次 Full GC 后释放很多内存就代表正常吗? 不能只看一次释放量,还要看触发频率、回收后基线、暂停时间和业务分配速率。
- 追问:线上首先应该关注哪个指标? 同时看回收后占用趋势、GC 原因、频率、暂停分位数和分配/晋升速率。
面试回答的稳妥顺序是“常见定义 → 术语边界 → 收集器实例 → 日志诊断”,不要把某个 HotSpot 版本的日志习惯说成 JVM 规范。
八、加强记忆
先用“Young 看年轻代、Mixed 加部分老年代、Full 走全堆级兜底”建立范围感,再马上补上 Major 等术语没有跨收集器统一定义。Young GC 频繁并不天然异常,因为分代设计本来就利用低存活率快速回收;Full GC 频繁才需要结合触发原因、回收后基线和暂停时间调查。G1 的 Mixed GC 只选择部分老年代 Region,不能与 Full GC 混用。诊断时不要只读名字,要同时看收集器、原因、前后容量、分配晋升速率和长期趋势。