Minor GC vs Major GC vs Full GC
你的应用有没有遇到过 GC 停顿?
JVM 的 GC 分为几种类型,每种类型的停顿时间、触发条件、对性能的影响都不同。理解它们的区别,是排查 GC 问题的第一步。
一、三种 GC 类型
1.1 Minor GC(Young GC)
定义:清理新生代的垃圾。
触发条件:Eden 区空间不足。
Minor GC 流程:
┌─────────────────────────────────────────────────────────────┐
│ Minor GC 前 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Eden │ S0 │ S1 │ 老年代 │ │
│ │ 满 │ 有数据 │ 空 │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ↓ Minor GC 执行 │
│ 存活对象复制到 S1,年龄+1 │
│ S0 清空 │
│ │
│ Minor GC 后 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Eden │ S0 │ S1 │ 老年代 │ │
│ │ 空 │ 空 │ 存活对象 │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ S0 和 S1 角色交换 │
└─────────────────────────────────────────────────────────────┘特点:
- 频率高(可能几秒一次)
- STW 时间短(通常几十毫秒)
- 复制算法,高效
1.2 Major GC vs Full GC
这是一个容易混淆的点。JVM 规范中没有明确定义 Major GC,但业界通常这样区分:
| 类型 | 定义 |
|---|---|
| Major GC | 清理老年代的垃圾(JVM 没有严格定义,常见于 CMS 收集器的并发标记阶段) |
| Full GC | 清理整个堆(年轻代 + 老年代)以及方法区 |
Major GC 和 Full GC 的关系:
Major GC 通常指老年代的收集,但它可能引发 Full GC(当空间不足时)
┌─────────────────────────────────────────────────────────────┐
│ │
│ Major GC ──→ 老年代空间不足 ──→ 触发 Full GC │
│ │
│ Full GC ──→ 清理整个堆 + 方法区 │
│ ──→ 包括年轻代、老年代、Metaspace │
│ │
└─────────────────────────────────────────────────────────────┘二、Full GC 触发条件
Full GC 是最"贵"的 GC,停顿时间长,触发条件也最多:
2.1 老年代空间不足
这是最常见的 Full GC 原因。
当对象需要晋升老年代,但老年代空间不足时,触发 Full GC。
┌─────────────────────────────────────────────────────────────┐
│ 场景:大对象直接进入老年代 │
│ │
│ 老年代空间不足 │
│ ┌─────────────────────────────┐ │
│ │ ██████████████████████████ │ ← 空间不足 │
│ │ 新对象 ──→ 需要 100MB │ │
│ │ 老年代只有 50MB 可用 │ │
│ └─────────────────────────────┘ │
│ ↓ │
│ 触发 Full GC 尝试回收 │
└─────────────────────────────────────────────────────────────┘2.2 方法区空间不足(JDK 7 及之前)
PermGen 空间不足时,触发 Full GC。
常见场景:
- 大量类加载(如动态代理、JSP)
- 字符串常量池膨胀
2.3 System.gc() 调用
调用 System.gc() 或 Runtime.getRuntime().gc() 会建议 JVM 执行 Full GC。
public class GCDemo {
public static void main(String[] args) {
// 建议 JVM 执行 GC,但不保证立即执行
System.gc();
// 注意:这不一定触发 Full GC,JVM 可以忽略这个建议
}
}注意:
System.gc()只是一个建议,JVM 可以完全忽略。如果想强制 Full GC,可以使用-XX:+DisableExplicitGC参数。
2.4 空间分配担保失败
Minor GC 前,如果检查到老年代最大可用连续空间 < 新生代总空间,且历次晋升到老年代的对象平均大小 > 老年代可用空间,触发 Full GC。
2.5 CMS GC 失败
CMS 收集器在并发标记阶段,如果老年代空间不足,会触发 concurrent mode failure,回退为 Serial Old 收集器执行 Full GC。
CMS GC 失败场景:
┌─────────────────────────────────────────────────────────────┐
│ 并发标记阶段 │
│ ↓ │
│ 用户线程继续运行,对象继续分配 │
│ ↓ │
│ 老年代空间不足,无法继续并发标记 │
│ ↓ │
│ concurrent mode failure │
│ ↓ │
│ Stop The World → Serial Old 收集器执行 Full GC │
└─────────────────────────────────────────────────────────────┘三、Minor GC / Major GC / Full GC 的关系
┌─────────────────────────────────────────────────────────────┐
│ │
│ Eden 区满 ──→ Minor GC ──→ 存活对象 → Survivor ──→ 老年代 │
│ (STW 短) │
│ │
│ 老年代满 ──→ Major GC / Full GC ──→ 整个堆回收 │
│ (STW 长) │
│ │
│ 方法区满 ──→ Full GC(包含方法区) │
│ │
│ System.gc() ──→ 可能是 Full GC │
│ │
└─────────────────────────────────────────────────────────────┘四、G1 中的特殊之处
在 G1 收集器中,没有传统的 Minor GC / Major GC / Full GC 区分。
G1 使用的是 Garbage First 策略——它把堆划分为多个 Region,每次回收垃圾比例最高的 Region。
G1 的 GC 类型:
Young GC:
- Eden 区 Region 满
- 存活对象复制到 Survivor Region
- Stop The World(但时间可控)
Mixed GC:
- 老年代 Region 占比超过阈值
- 回收所有年轻代 Region + 部分老年代 Region
- Stop The World
Full GC(G1 不希望发生):
- 存活对象移动失败(找不到空闲 Region)
- 触发 Serial Old Full GC
- 这是 G1 尽力避免的情况五、GC 日志解读
5.1 Minor GC 日志
[GC (Allocation Failure) [DefNew: 65536K->8704K(73728K), 0.0456789 secs] 131072K->72192K(262144K), 0.0459012 secs] [Times: user=0.12 sys=0.01, real=0.05 secs]解读:
Allocation Failure:Eden 区分配失败,触发 Minor GCDefNew:Serial 收集器(年轻代)65536K->8704K:GC 前使用 65536K,GC 后使用 8704K(73728K):年轻代总大小131072K->72192K:整个堆 GC 前 131072K,GC 后 72192K0.0459012 secs:GC 停顿时间
5.2 Full GC 日志
[Full GC (Allocation Failure) [CMS: 1572864K->1048576K(1572864K), 1.2345678 secs] 1835008K->1048576K(2097152K), [Metaspace: 524288K->524288K(1048576K)], 1.2356789 secs]解读:
Full GC:Full GCCMS:CMS 收集器1572864K->1048576K:老年代 GC 前 1572864K,GC 后 1048576KMetaspace: 524288K->524288K:元空间没有变化
六、面试高频问题
问题 1:Minor GC 和 Full GC 有什么区别?
| Minor GC | Full GC | |
|---|---|---|
| 回收范围 | 新生代 | 整个堆 + 方法区 |
| 触发频率 | 高(Eden 满) | 低 |
| STW 时间 | 短(几十毫秒) | 长(可能秒级) |
| 算法 | 复制算法 | 标记-整理 |
问题 2:什么时候会触发 Full GC?
- 老年代空间不足
- 方法区空间不足(JDK 7)
- System.gc() 调用
- 空间分配担保失败
- CMS GC 失败
问题 3:G1 中有 Full GC 吗?
有,但 G1 尽力避免 Full GC。G1 正常情况下的 Mixed GC 足以处理老年代的垃圾回收。Full GC 只在 Mixed GC 无法回收(找不到空闲 Region)时才会触发,通常意味着需要调优。
问题 4:如何减少 Full GC 的频率?
- 增大堆内存
- 优化对象生命周期(减少大对象、减少长生命周期对象)
- 调整新生代/老年代比例
- 使用 G1 或 ZGC 减少 Full GC 影响
留给你的问题
我们讲了 Minor GC、Major GC 和 Full GC 的区别。
你有没有想过:为什么 Full GC 的停顿时间通常比 Minor GC 长很多?
原因是多方面的:
- Full GC 回收的内存区域更大
- Full GC 使用的算法(标记-整理)比 Minor GC(复制)慢
- Full GC 可能还要清理方法区
- 老年代通常比新生代大很多倍
但如果你发现 Minor GC 的停顿时间也很长(比如超过 100ms),这可能意味着:
- Survivor 区太小,对象频繁晋升到老年代
- 新生代太大,复制算法耗时增加
- 存在内存碎片问题
下一节,我们来聊聊垃圾收集算法的具体实现——标记-清除、复制、标记-整理。
