在 JVM 中,垃圾收集(Garbage Collection, GC)算法的核心目标是自动回收无用对象的内存,同时尽量减少对应用性能的影响。以下是 JVM 中主要垃圾收集算法的原理、流程及实际应用场景的详细介绍:
一、标记-清除算法(Mark-Sweep)
原理
- 标记阶段:从 GC Roots(如栈引用、静态变量)出发,遍历对象图,标记所有存活对象。
- 清除阶段:扫描堆内存,回收未被标记的对象所占用的内存(直接释放,不整理内存)。
工作流程
- 暂停所有应用线程(STW,Stop-The-World),启动标记。
- 标记完成后,清除未标记的垃圾对象。
- 恢复应用线程。
优点
- 实现简单,内存回收效率较高。
缺点
- 内存碎片化:清除后会产生不连续的内存空间,可能无法分配大对象,最终触发 Full GC。
- 两次 STW:标记和清除阶段均需暂停应用线程。
应用场景
- CMS 收集器的老年代回收(仅清除阶段,标记阶段并发执行)。
二、标记-整理算法(Mark-Compact)
原理
- 标记阶段:与标记-清除相同,标记所有存活对象。
- 整理阶段:将所有存活对象向内存一端移动,清理边界外的内存(消除碎片)。
工作流程
- STW 标记存活对象。
- 将存活对象移动到内存一端,保持连续。
- 清除边界外的内存。
- 恢复应用线程。
优点
- 无内存碎片:整理后内存连续,适合长期运行的应用。
- 空间利用率高:减少大对象分配失败的概率。
缺点
- 两次 STW 且时间长:移动对象需要额外时间,尤其是堆较大时。
- 内存移动开销:对象复制可能影响吞吐量。
应用场景
- Serial Old、Parallel Old 收集器的老年代回收。
- G1 的混合回收阶段(复制存活对象到新 Region)。
三、复制算法(Copying)
原理
- 将堆内存分为两块(From 和 To 空间),每次只使用其中一块。
- 存活对象复制:将 From 空间的存活对象复制到 To 空间,并保持紧凑排列。
- 清空 From 空间:复制完成后,直接清空 From 空间。
工作流程
- STW 标记 From 空间的存活对象。
- 将存活对象按顺序复制到 To 空间。
- 交换 From 和 To 空间的角色。
- 恢复应用线程。
优点
- 无碎片:对象在 To 空间连续排列。
- 高效回收:只需遍历存活对象,适合存活率低的场景。
缺点
- 内存浪费:需预留一半内存作为复制空间,实际可用内存仅 50%。
- 存活率高时效率低:若多数对象存活,复制成本高。
应用场景
- 年轻代回收(如 Serial、ParNew、Parallel Scavenge 收集器)。
- G1 的年轻代 Region 回收。
四、分代收集算法(Generational Collection)
核心思想
基于对象的生命周期特性,将堆划分为不同区域(年轻代、老年代),对不同代采用不同算法:
- 年轻代:对象存活率低,使用复制算法。
- 老年代:对象存活率高,使用标记-清除或标记-整理算法。
分代依据
- 弱分代假说(Weak Generational Hypothesis):绝大多数对象朝生夕死。
- 强分代假说(Strong Generational Hypothesis):多次存活的对象倾向于继续存活。
工作流程
- 年轻代 Minor GC:频繁触发,使用复制算法回收 Eden 和 Survivor 区。
- 老年代 Major GC/Full GC:触发频率低,使用标记-清除或标记-整理算法。
应用场景
- 所有现代 JVM 收集器的默认策略(如 CMS、G1、ZGC 等)。
五、增量收集算法(Incremental Collecting)
原理
- 将垃圾回收过程分为多个小步骤,与应用线程交替执行。
- 每次仅回收部分垃圾,减少单次 STW 时间。
优点
- 降低延迟:每次暂停时间短,适合实时性要求高的场景。
缺点
- 总体吞吐量下降:频繁切换线程导致额外开销。
- 实现复杂:需处理应用线程与回收线程的交互。
应用场景
- CMS 的并发标记阶段(并发执行,但需最终 STW 修正)。
- Shenandoah 收集器(并发复制对象)。
六、分区收集算法(Region-Based)
原理
- 将堆划分为多个独立区域(Region),优先回收垃圾比例高的区域(Garbage-First 原则)。
- 结合分代思想,动态分配区域角色(Eden、Survivor、Old)。
优点
- 可预测停顿:通过控制每次回收的区域数量,满足
MaxGCPauseMillis
目标。 - 高效管理大堆:避免全堆扫描,减少 STW 时间。
应用场景
- G1 收集器:分 Region 管理,混合回收年轻代和老年代。
- ZGC/Shenandoah:基于类似思想,扩展为全堆并发回收。
七、算法对比与选型
算法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
标记-清除 | 实现简单,无移动开销 | 内存碎片,两次 STW | 老年代(CMS) |
标记-整理 | 无碎片,空间利用率高 | STW 时间长,移动开销大 | 老年代(Serial Old) |
复制 | 无碎片,回收效率高 | 内存浪费,存活率高时低效 | 年轻代(所有分代收集器) |
分代收集 | 结合对象生命周期优化 | 需维护分代结构 | 所有现代收集器 |
增量收集 | 低延迟 | 吞吐量下降 | 实时系统(Shenandoah) |
分区收集 | 可预测停顿,大堆友好 | 元数据管理复杂 | G1、ZGC、Shenandoah |
总结
JVM 的垃圾收集算法是内存管理的核心,不同算法在吞吐量、延迟和内存开销之间权衡。实际应用中,分代收集与分区算法(如 G1)成为主流,而 ZGC/Shenandoah 进一步通过并发和压缩技术突破停顿时间限制。选择算法需结合应用场景(如堆大小、延迟要求)及 JVM 版本特性。
以下以CMS和G1垃圾收集器的原理与工作流程举例:
CMS(Concurrent Mark-Sweep)收集器
设计目标
- 低延迟:通过并发标记和清除减少停顿时间(STW),适用于对响应时间敏感的应用(如Web服务)。
- 老年代专用:仅管理老年代,需与年轻代收集器(如ParNew)配合使用。
核心原理
- 标记-清除算法
- 通过标记存活对象,直接清除未标记的垃圾对象,不进行内存整理,可能导致内存碎片。
- 并发执行
- 大部分垃圾回收阶段与应用线程并发执行,减少STW时间。
工作流程(4个阶段)
初始标记(Initial Mark)
- STW:短暂暂停,标记从GC Roots直接可达的对象(如栈引用、静态变量)。
- 依赖年轻代回收:通常与年轻代的Minor GC同步触发,避免全堆扫描。
并发标记(Concurrent Mark)
- 并发执行:遍历老年代对象图,标记所有存活对象(与应用线程并行)。
- 可能漏标:并发阶段应用线程可能修改对象引用,需后续修正。
重新标记(Remark)
- STW:修正并发标记期间因应用线程运行导致的标记变化(使用增量更新或原始快照算法)。
- 时间较长:需处理所有变更,是CMS中最耗时的STW阶段。
并发清除(Concurrent Sweep)
- 并发执行:清除未标记的垃圾对象,回收内存空间。
- 不整理内存:清除后产生内存碎片,需定期Full GC(使用Serial Old)进行压缩。
关键问题
- 内存碎片:长期运行后可能触发Full GC,导致长时间停顿。
- 浮动垃圾:并发阶段新产生的垃圾需等到下次回收。
- CPU竞争:并发阶段占用CPU资源,可能影响应用吞吐量。
G1(Garbage-First)收集器
设计目标
- 平衡吞吐量与延迟:通过分Region和可预测停顿,适应大堆内存(4GB+)场景。
- 全堆管理:统一管理年轻代和老年代,动态分配Region角色。
核心原理
分Region模型
- 堆被划分为多个大小固定(1MB~32MB)的Region,每个Region可以是Eden、Survivor或Old类型。(堆/2048)
- Humongous区域:存储大于Region 50%的大对象。
标记-整理算法
- 通过复制存活对象到新Region,避免内存碎片。
停顿预测模型
- 根据用户设定的
MaxGCPauseMillis
,优先回收垃圾比例高的Region(Garbage-First原则)。
- 根据用户设定的
工作流程(3种操作模式)
G1的回收过程分为三种操作:年轻代回收(Young GC)、并发标记周期(Concurrent Marking Cycle) 和 混合回收(Mixed GC)。
1. 年轻代回收(Young GC)
- STW:暂停所有应用线程,复制Eden和Survivor区的存活对象到新Survivor或Old Region。
- 触发条件:Eden区占满时触发。
2. 并发标记周期(Concurrent Marking Cycle)
初始标记(Initial Mark)
- STW:标记GC Roots直接可达的对象(与Young GC同步触发,复用Young GC的暂停)。
根区域扫描(Root Region Scan)
- 扫描Survivor区(作为GC Roots的一部分),确保并发标记的正确性。
并发标记(Concurrent Mark)
- 并发执行:遍历堆中所有存活对象,标记存活状态。
最终标记(Final Mark)
- STW:处理SATB(Snapshot-At-The-Beginning)队列中的引用变更,确保标记准确性。
清除阶段(Cleanup)
- 部分STW:统计各Region的垃圾比例,识别高价值回收区域。
- 不回收内存:仅选择待回收的Region,为混合回收做准备。
3. 混合回收(Mixed GC)
- STW:同时回收年轻代和部分老年代Region(根据垃圾比例选择)。
- 多次执行:可能分多次回收,直到满足
MaxGCPauseMillis
目标。
关键优势
- 内存整理:通过复制算法避免碎片。
- 可预测停顿:动态调整回收的Region数量以满足停顿目标。
- 大堆优化:分Region设计适合管理超大堆内存。
CMS与G1对比
特性 | CMS | G1 |
---|---|---|
堆结构 | 传统分代(年轻代+老年代) | 分Region,逻辑分代 |
算法 | 标记-清除(碎片需Full GC整理) | 标记-整理(复制算法,无碎片) |
停顿控制 | 不可预测 | 通过MaxGCPauseMillis 预测 |
适用场景 | 中小堆,低延迟优先 | 大堆(4GB+),平衡吞吐量和延迟 |
内存开销 | 低 | 较高(Region元数据) |
Full GC触发 | 内存不足/碎片时触发Serial Old | 尽量避免,依赖并发周期 |
JDK支持 | 已废弃(JDK 14移除) | JDK 9+默认 |
总结
- CMS:通过并发标记-清除减少停顿,适合中小堆且延迟敏感的场景,但面临碎片和Full GC风险。
- G1:分Region模型和可预测停顿设计,适合大堆内存,平衡吞吐量与延迟,是JDK 9+的默认选择。