垃圾收集算法与收集器

发布于:2025-03-11 ⋅ 阅读:(20) ⋅ 点赞:(0)

在 JVM 中,垃圾收集(Garbage Collection, GC)算法的核心目标是自动回收无用对象的内存,同时尽量减少对应用性能的影响。以下是 JVM 中主要垃圾收集算法的原理、流程及实际应用场景的详细介绍:


一、标记-清除算法(Mark-Sweep)

原理
  • 标记阶段:从 GC Roots(如栈引用、静态变量)出发,遍历对象图,标记所有存活对象。
  • 清除阶段:扫描堆内存,回收未被标记的对象所占用的内存(直接释放,不整理内存)。
工作流程
  1. 暂停所有应用线程(STW,Stop-The-World),启动标记。
  2. 标记完成后,清除未标记的垃圾对象。
  3. 恢复应用线程。
优点
  • 实现简单,内存回收效率较高。
缺点
  • 内存碎片化:清除后会产生不连续的内存空间,可能无法分配大对象,最终触发 Full GC。
  • 两次 STW:标记和清除阶段均需暂停应用线程。
应用场景
  • CMS 收集器的老年代回收(仅清除阶段,标记阶段并发执行)。

二、标记-整理算法(Mark-Compact)

原理
  • 标记阶段:与标记-清除相同,标记所有存活对象。
  • 整理阶段:将所有存活对象向内存一端移动,清理边界外的内存(消除碎片)。
工作流程
  1. STW 标记存活对象。
  2. 将存活对象移动到内存一端,保持连续。
  3. 清除边界外的内存。
  4. 恢复应用线程。
优点
  • 无内存碎片:整理后内存连续,适合长期运行的应用。
  • 空间利用率高:减少大对象分配失败的概率。
缺点
  • 两次 STW 且时间长:移动对象需要额外时间,尤其是堆较大时。
  • 内存移动开销:对象复制可能影响吞吐量。
应用场景
  • Serial OldParallel Old 收集器的老年代回收。
  • G1 的混合回收阶段(复制存活对象到新 Region)。

三、复制算法(Copying)

原理
  • 将堆内存分为两块(From 和 To 空间),每次只使用其中一块。
  • 存活对象复制:将 From 空间的存活对象复制到 To 空间,并保持紧凑排列。
  • 清空 From 空间:复制完成后,直接清空 From 空间。
工作流程
  1. STW 标记 From 空间的存活对象。
  2. 将存活对象按顺序复制到 To 空间。
  3. 交换 From 和 To 空间的角色。
  4. 恢复应用线程。
优点
  • 无碎片:对象在 To 空间连续排列。
  • 高效回收:只需遍历存活对象,适合存活率低的场景。
缺点
  • 内存浪费:需预留一半内存作为复制空间,实际可用内存仅 50%。
  • 存活率高时效率低:若多数对象存活,复制成本高。
应用场景
  • 年轻代回收(如 Serial、ParNew、Parallel Scavenge 收集器)。
  • G1 的年轻代 Region 回收

四、分代收集算法(Generational Collection)

核心思想

基于对象的生命周期特性,将堆划分为不同区域(年轻代、老年代),对不同代采用不同算法:

  1. 年轻代:对象存活率低,使用复制算法
  2. 老年代:对象存活率高,使用标记-清除标记-整理算法。
分代依据
  • 弱分代假说(Weak Generational Hypothesis):绝大多数对象朝生夕死。
  • 强分代假说(Strong Generational Hypothesis):多次存活的对象倾向于继续存活。
工作流程
  1. 年轻代 Minor GC:频繁触发,使用复制算法回收 Eden 和 Survivor 区。
  2. 老年代 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)配合使用。

核心原理
  1. 标记-清除算法
    • 通过标记存活对象,直接清除未标记的垃圾对象,不进行内存整理,可能导致内存碎片。
  2. 并发执行
    • 大部分垃圾回收阶段与应用线程并发执行,减少STW时间。

工作流程(4个阶段)
  1. 初始标记(Initial Mark)

    • STW:短暂暂停,标记从GC Roots直接可达的对象(如栈引用、静态变量)。
    • 依赖年轻代回收:通常与年轻代的Minor GC同步触发,避免全堆扫描。
  2. 并发标记(Concurrent Mark)

    • 并发执行:遍历老年代对象图,标记所有存活对象(与应用线程并行)。
    • 可能漏标:并发阶段应用线程可能修改对象引用,需后续修正。
  3. 重新标记(Remark)

    • STW:修正并发标记期间因应用线程运行导致的标记变化(使用增量更新或原始快照算法)。
    • 时间较长:需处理所有变更,是CMS中最耗时的STW阶段。
  4. 并发清除(Concurrent Sweep)

    • 并发执行:清除未标记的垃圾对象,回收内存空间。
    • 不整理内存:清除后产生内存碎片,需定期Full GC(使用Serial Old)进行压缩。

关键问题
  • 内存碎片:长期运行后可能触发Full GC,导致长时间停顿。
  • 浮动垃圾:并发阶段新产生的垃圾需等到下次回收。
  • CPU竞争:并发阶段占用CPU资源,可能影响应用吞吐量。

G1(Garbage-First)收集器

设计目标
  • 平衡吞吐量与延迟:通过分Region和可预测停顿,适应大堆内存(4GB+)场景。
  • 全堆管理:统一管理年轻代和老年代,动态分配Region角色。

核心原理
  1. 分Region模型

    • 堆被划分为多个大小固定(1MB~32MB)的Region,每个Region可以是Eden、Survivor或Old类型。(堆/2048)
    • Humongous区域:存储大于Region 50%的大对象。
  2. 标记-整理算法

    • 通过复制存活对象到新Region,避免内存碎片。
  3. 停顿预测模型

    • 根据用户设定的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)
  1. 初始标记(Initial Mark)

    • STW:标记GC Roots直接可达的对象(与Young GC同步触发,复用Young GC的暂停)。
  2. 根区域扫描(Root Region Scan)

    • 扫描Survivor区(作为GC Roots的一部分),确保并发标记的正确性。
  3. 并发标记(Concurrent Mark)

    • 并发执行:遍历堆中所有存活对象,标记存活状态。
  4. 最终标记(Final Mark)

    • STW:处理SATB(Snapshot-At-The-Beginning)队列中的引用变更,确保标记准确性。
  5. 清除阶段(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+的默认选择。