Skip to content

MinorGC/MajorGC/FullGC 对比

三种 GC 的区别

很多初学者对 MinorGC、MajorGC、FullGC 三个概念混淆不清。这一节把它们放在一起对比,厘清概念。

MinorGC:年轻代的垃圾回收

触发条件

Eden 区空间不足 时触发 MinorGC。

java
public class MinorGCDemo {
    public static void main(String[] args) {
        // 不断创建对象,直到 Eden 区满
        List<byte[]> list = new ArrayList<>();
        int i = 0;
        while (true) {
            list.add(new byte[1024 * 1024]);  // 每次分配 1MB
            i++;
            // 当 Eden 区满,触发 MinorGC
        }
    }
}

回收过程

MinorGC 采用复制算法

  1. 扫描 Eden 区和 From Survivor 区,找出所有存活对象
  2. 存活对象复制到 To Survivor 区
  3. Eden 区和 From Survivor 区清空
  4. From Survivor 和 To Survivor 角色互换
MinorGC 前:
┌──────────────────────────────────────────────────────────┐
│ Eden (满)        │   S0 (部分存活)   │   S1 (空)      │
│ [ObjA][ObjB]... │   [ObjC]         │               │
└──────────────────────────────────────────────────────────┘

MinorGC 后:
┌──────────────────────────────────────────────────────────┐
│ Eden (已清空)     │   S0 (空)        │   S1 (存活对象)  │
└──────────────────────────────────────────────────────────┘

              ObjA, ObjB, ObjC 全部复制到这里

关键特点

  • 停顿时间短:年轻代通常较小,扫描和复制速度快(通常几十毫秒)
  • 频率高:MinorGC 非常频繁,是日常 GC 的主力
  • 对象晋升:存活超过 Survivor 区容量或年龄达到阈值 → 晋升老年代

MajorGC / OldGC:老年代的垃圾回收

触发条件

老年代空间不足时触发 MajorGC(或 OldGC)。

java
public class MajorGCDemo {
    public static void main(String[] args) {
        // 模拟大量对象晋升老年代
        List<byte[]> bigObjects = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            // 分配大对象,直接进入老年代
            bigObjects.add(new byte[10 * 1024 * 1024]);  // 10MB × 100 = 1GB
        }
    }
}

回收过程

MajorGC 通常采用标记-清除算法标记-压缩算法

  1. 扫描老年代,标记所有存活对象
  2. 清除未标记的对象(或者压缩存活对象)
  3. 如果空间仍然不足,触发 FullGC

MajorGC 的速度比 MinorGC 慢得多,因为老年代的容量通常是年轻代的 2~3 倍。

FullGC:全面的垃圾回收

触发条件

FullGC 是最重量级的 GC,触发条件很多:

  1. 老年代空间不足:MinorGC 晋升时担保失败
  2. System.gc():调用系统 GC(不一定生效,取决于 JVM 实现)
  3. 元空间不足:JDK 8+ 元空间满了
  4. 代码缓存不足:JIT 编译器没有足够的空间缓存编译后的代码
  5. 手动触发:RMI、JMX 等定时 FullGC
java
public class FullGCDemo {
    public static void main(String[] args) {
        // 触发 FullGC 的几种方式:

        // 1. System.gc()
        System.gc();  // 建议执行 FullGC,不保证

        // 2. Runtime.getRuntime().gc()
        Runtime.getRuntime().gc();

        // 3. JMX 手动触发
        // ManagementFactory.getGarbageCollectorMXBeans()
        //     .forEach(gc -> gc.collect());
    }
}

FullGC 的全面性

FullGC 会扫描整个堆(年轻代 + 老年代)和元空间,停顿时间可能从几百毫秒到几秒不等。

MinorGC:只扫描年轻代(Eden + Survivor)
MajorGC:只扫描老年代
FullGC:扫描整个堆 + 元空间

三种 GC 的对比

维度MinorGCMajorGCFullGC
触发条件Eden 区满老年代满老年代满/元空间满/显式调用
回收区域年轻代老年代整个堆 + 元空间
回收算法复制算法标记-清除/压缩视 GC 收集器而定
停顿时间短(几十毫秒)中等(数百毫秒)长(数百毫秒到秒级)
发生频率高(每分钟多次)低(每小时几次)低(每天几次或更少)
是否 Stop-The-World是(但时间短)是(CMS 等并发时不同)是(且时间长)

Stop-The-World:GC 的代价

所有 GC 都会触发 Stop-The-World(STW)——暂停所有应用线程,直到 GC 完成。

java
public class STWDemo {
    public static void main(String[] args) {
        // STW 期间,整个程序停顿
        // GC 线程工作,所有 Java 线程暂停
        // 线程无法响应请求,无法分配内存
        // ...
    }
}

不同 GC 的 STW 表现

GC 收集器STW 程度
Serial GC全程 STW,最长停顿
Parallel GCFullGC 时 STW,并行缩短停顿
CMS GC初始标记 + 最终标记 STW,并发阶段不 STW
G1 GC初始标记 + 最终标记 + cleanup 部分 STW
ZGC / Shenandoah极短 STW(亚毫秒级)

实战建议

  1. MinorGC 频繁是正常的:说明 Eden 区合理利用
  2. MinorGC 后大量对象晋升要警惕:说明 Survivor 区太小或对象生命周期异常
  3. FullGC 频繁是问题:需要调优或升级 GC 收集器
  4. 使用 G1:JDK 11+ 默认,平衡吞吐和停顿

本节小结

三种 GC 的核心区别在于扫描范围停顿时间

  • MinorGC:年轻代,频率高,时间短
  • MajorGC:老年代,频率低,时间中等
  • FullGC:全堆 + 元空间,频率最低,时间最长

理解三种 GC 的区别,是理解 GC 调优的基础。下一节,我们来看 堆空间 GC 举例与日志分析

基于 VitePress 构建