Skip to content

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。

java
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 GC
  • DefNew:Serial 收集器(年轻代)
  • 65536K->8704K:GC 前使用 65536K,GC 后使用 8704K
  • (73728K):年轻代总大小
  • 131072K->72192K:整个堆 GC 前 131072K,GC 后 72192K
  • 0.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 GC
  • CMS:CMS 收集器
  • 1572864K->1048576K:老年代 GC 前 1572864K,GC 后 1048576K
  • Metaspace: 524288K->524288K:元空间没有变化

六、面试高频问题

问题 1:Minor GC 和 Full GC 有什么区别?

Minor GCFull 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 长很多?

原因是多方面的:

  1. Full GC 回收的内存区域更大
  2. Full GC 使用的算法(标记-整理)比 Minor GC(复制)慢
  3. Full GC 可能还要清理方法区
  4. 老年代通常比新生代大很多倍

但如果你发现 Minor GC 的停顿时间也很长(比如超过 100ms),这可能意味着:

  • Survivor 区太小,对象频繁晋升到老年代
  • 新生代太大,复制算法耗时增加
  • 存在内存碎片问题

下一节,我们来聊聊垃圾收集算法的具体实现——标记-清除、复制、标记-整理。

基于 VitePress 构建