JVM GC 核心体系:从算法原理到收集器选型实战
线上服务遇到性能抖动、接口 RT 飙升甚至雪崩时,追根溯源往往能发现 JVM 垃圾回收的踪迹。初学者常困于收集器、Minor/Full GC、标记算法等概念的迷雾中,搞不清它们之间的层级关系。为什么换个收集器后延迟立刻下降?这篇文章不做枯燥的概念罗列,而是从工程视角出发,把'算法 → GC 类型 → 收集器'这一整套体系串起来,帮你建立可落地的 GC 理解框架。
一、理清主线:算法、范围与执行者
理解 GC 的关键在于区分三个层次,它们不是并列概念,而是逐层依赖:
- 底层算法:决定对象如何被识别、移动或清理
- GC 类型:决定本次回收的内存范围
- 收集器:负责把算法与回收范围结合并真正执行
收集器会根据回收区域的特点,选择合适的算法来完成不同类型的 GC。这种分层设计让 JVM 能够灵活适配不同的业务场景。
二、底层回收算法:解决'怎么回收'
所有 GC 行为最终都要落到具体算法上。不同算法适配不同的对象存活特征与性能目标。
1. 标记 - 清除
流程是先标记不可达对象,再直接释放内存。实现简单且无需移动对象,但会产生内存碎片。长期运行后可能导致分配困难,尤其在存活对象较多的区域,清理成本会变高。
2. 标记 - 复制
把内存分为两块,仅使用其中一块。回收时将存活对象复制到另一块,然后整体清空旧区域。这种方式没有碎片,回收效率高,但内存利用率较低,适合对象生命周期很短的场景,例如新生代。
3. 标记 - 整理
先标记,再把存活对象向一端压缩,最后清理边界外的垃圾。它能解决碎片问题,适合老年代,但对象移动成本较高,通常伴随较长的 STW(Stop The World)停顿。
4. 并发重定位(现代 GC 核心)
这是在复制与整理思想上的进一步优化。通过并发移动对象和指针重映射,使对象迁移不再完全依赖长时间停顿。现代低延迟 GC(如 ZGC、Shenandoah)大量使用这类机制,以实现低停顿与高内存利用率的平衡。
工程理解:算法没有绝对优劣,关键在于对象存活率与停顿目标。存活率低选复制,碎片敏感选整理或重定位,大内存低延迟则看并发移动能力。
三、GC 类型:界定'回收哪里'
GC 类型关注的是回收范围,而不是实现方式。
1. Minor GC(新生代)
主要发生在 Eden 区填满时。新生代对象大多'朝生夕死',存活率低,因此回收通常较快。停顿时间一般较短,但如果触发频繁,仍然会对吞吐产生影响。
2. Major GC(老年代)
针对老年代的回收操作。在很多收集器中,它不会单独频繁触发,而是常与 Full GC 或混合回收一起出现。
3. Full GC(全堆)
覆盖新生代、老年代甚至元空间的完整回收。这是最昂贵的操作之一,通常意味着内存压力已经很大,例如老年代空间不足、晋升失败或手动调用 System.gc()。
工程经验:线上性能问题往往不是"GC 慢",而是 Full GC 触发时机不合理。
四、收集器:真正执行 GC 的角色
收集器是 JVM 提供的具体实现,它会把算法与回收范围组合起来,并根据目标优化性能。
1. 传统分代收集器
- Serial:单线程、简单直接,适合小内存或客户端应用。
- Parallel:强调吞吐量,适用于批处理或后台计算场景。新生代常用复制算法,老年代使用整理。
- CMS(已淘汰):追求低停顿的老年代收集器。通过并发标记与清除降低停顿时间,但碎片问题严重。当碎片过多时,会退化为单线程 Full GC,导致长时间卡顿,这也是它被替换的重要原因。

