JVM 垃圾回收算法有哪些?
2026年02月14日
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
面试考察点
- 基础概念理解:是否清楚 JVM 垃圾回收要解决什么问题(识别垃圾、回收内存),以及几种基本算法的核心思想。
- 算法优缺点分析:能否准确说出每种算法的优势(如吞吐量、低延迟、内存利用率)和劣势(如内存碎片、STW 时间),而不仅仅是背定义。
- 分代收集策略:是否理解为什么现代 JVM 不单独使用某一种算法,而是基于 “分代假设” 将不同算法组合起来(如在年轻代用复制算法,在老年代用标记-整理或标记-清除)。
- 与具体垃圾回收器的关联:是否了解这些算法在具体垃圾回收器(如 Serial、Parallel、CMS、G1、ZGC)中的落地实现,以及版本演进(JDK 8 的 Parallel 默认、JDK 9 后的 G1 默认)。
- 实际应用场景:能否根据系统对吞吐量、延迟敏感度的不同,联想到应该侧重选择基于哪种算法的回收器。
核心答案
JVM 的垃圾回收算法主要包含以下四种基础算法:
- 标记-清除(Mark-Sweep)
- 复制(Copying)
- 标记-整理(Mark-Compact)
- 分代收集(Generational Collection) —— 这其实是一种策略,融合了前几种算法。
目前主流的垃圾回收器(如 G1、ZGC)还引入了并发标记、并发整理等更细粒度的算法变体,但其底层原理仍脱胎于上述基础算法。
深度解析
1. 标记-清除(Mark-Sweep)
- 原理/机制:
- 标记阶段:从 GC Roots 出发,遍历所有可达对象,并将它们标记为存活。
- 清除阶段:遍历堆中所有对象,回收未被标记的对象的内存。这些被回收的内存会被记录到空闲列表(Free List)中,供后续分配使用。
- 图示(文字描述):假设内存块中有对象 A(存活)、B(存活)、C(死亡)。标记后,A 和 B 被标记;清除阶段,C 的内存被回收,但 A 和 B 之间的空间变成不连续的空闲块。
- 优缺点:
- 优点:实现简单,不需要移动对象。
- 缺点:
- 执行效率不稳定:标记和清除两个过程都需要遍历所有对象,当堆中对象数量庞大时,效率会下降。
- 内存碎片:清除后会产生大量不连续的内存碎片。当需要分配一个大对象时,即使总空闲内存足够,也可能因找不到连续空间而触发另一次垃圾回收。
- 应用场景:CMS(Concurrent Mark Sweep)回收器的 “清除” 阶段就基于此算法,因此 CMS 对碎片化敏感,最终可能导致 Concurrent Mode Failure 而退化为 Serial Old 进行整理。
2. 复制(Copying)
- 原理/机制:
- 将可用内存划分为大小相等的两块(如 From 和 To),每次只使用其中一块。
- 当这一块内存用完时,将存活对象复制到另一块空闲的内存上,然后一次性清理掉原来那块内存的所有对象。
- 复制时对象在内存中会被紧密排列,避免了碎片问题。
- 优缺点:
- 优点:
- 实现简单,运行高效。
- 内存回收时没有碎片。
- 只需遍历存活对象,对于存活率低的场景(如新生代),效率极高。
- 缺点:
- 空间浪费:内存可用空间缩小为原来的一半(如果严格按 1:1 分区)。
- 对象存活率高时效率下降:如果存活对象多,复制操作就会增多,得不偿失。因此该算法不适合老年代。
- 优点:
- 应用场景:几乎所有商业 JVM 的新生代回收器(Serial、ParNew、Parallel Scavenge)都采用这种算法。通常不按 1:1 划分,而是使用一个较大的 Eden 区和两个较小的 Survivor 区(如 HotSpot 默认 8:1:1),通过幸存区担保来优化空间利用率。
3. 标记-整理(Mark-Compact)
- 原理/机制:
- 标记阶段:与标记-清除一样,先标记出所有存活对象。
- 整理阶段:将所有存活对象向内存空间的一端移动,然后直接清理掉边界以外的所有内存。
- 优缺点:
- 优点:
- 解决了标记-清除的内存碎片问题。
- 与复制算法相比,没有内存分区浪费,内存利用率更高。
- 缺点:
- 停顿时间长:移动存活对象并更新所有引用(指针)是一项重量级操作,需要 Stop-The-World。
- 优点:
- 应用场景:老年代回收器(如 Serial Old、Parallel Old)以及 G1 的 “混合回收” 阶段的部分行为,都基于或借鉴了标记-整理的思想。
4. 分代收集(Generational Collection)
- 原理/机制:这并不是一种具体的算法,而是一种融合策略。它基于 “弱分代假说”(绝大多数对象朝生夕死,熬过多次 GC 的对象难以回收)。
- 新生代:对象存活率低,适合用复制算法,只需复制少量存活对象,效率高。
- 老年代:对象存活率高,没有额外空间进行分配担保,适合用标记-清除或标记-整理算法。
- 最佳实践:在实际开发中,我们通常通过配置不同的垃圾回收器组合来体现分代收集。例如:
- 高吞吐量场景(如批处理、计算任务):使用
-XX:+UseParallelGC(Parallel Scavenge + Parallel Old),新生代复制 + 老年代标记-整理。 - 低延迟场景(如 Web 服务、中间件):JDK 8 可使用
-XX:+UseConcMarkSweepGC(ParNew + CMS),新生代复制 + 老年代标记-清除(并发)。JDK 9+ 默认的 G1(Garbage-First)则是一种“化整为零”的算法,它将堆划分为多个 Region,逻辑上分代,但回收时优先回收价值最大的 Region,整体上更像是标记-整理的并发版本。
- 高吞吐量场景(如批处理、计算任务):使用
现代算法的演进(JDK 11+ 视角)
随着硬件发展和对延迟的极致追求,JVM 引入了更多不基于传统分代(或弱分代)的回收器,其算法也有演进:
- G1(Garbage-First):基于 Region 化的堆布局,整体上采用标记-整理算法,但通过并发标记、并发存活数据计算,能避免全堆的移动停顿。它通过 Remembered Set 来维护跨 Region 引用,实现了可预测的停顿时间模型。
- ZGC(JDK 11 引入) 和 Shenandoah(JDK 12 引入):它们更进一步,几乎在所有耗时操作(如标记、对象移动、引用更新)上都实现了并发处理,通过染色指针(ZGC)或读屏障(Shenandoah)等技术,将 Stop-The-World 时间降低到几乎忽略不计(10ms 以内)。其底层核心仍是并发标记和并发整理(移动对象)的组合。
常见误区
- 误区 1:认为 “分代收集” 是一个具体的算法。纠正:它是一种策略,组合了不同算法。
- 误区 2:认为 “标记-整理” 完全优于 “标记-清除”。纠正:标记-整理的吞吐量更低(因为要移动对象),且要求 Stop-The-World,而 CMS 的标记-清除是并发的。
- 误区 3:混淆 “复制算法” 与 “Survivor 区的作用”。纠正:复制算法是一种内存回收方式,而 Survivor 区是 HotSpot 实现新生代复制算法时的一种优化手段,用于减少复制次数和对象过早晋升。
总结
JVM 垃圾回收算法从基础的标记-清除、复制、标记-整理,发展到分代收集策略,再到如今 G1、ZGC 等并发回收器采用的并发标记/整理,核心目标始终是在内存利用率、吞吐量和响应延迟之间寻找最佳平衡。理解这些基础算法,是掌握 JVM 调优和排查内存问题的基石。