jvm垃圾回收算法?

JVM 垃圾回收算法:原理、实现与适用场景

JVM 垃圾回收(GC)的核心目标是识别并回收不再被引用的对象内存,同时尽可能降低对应用性能的影响。其算法设计围绕 "如何高效判定垃圾" 和 "如何高效回收垃圾" 两大核心问题展开,下面从基础原理到进阶实现,深入拆解核心算法及衍生优化。

一、前置:垃圾判定的核心准则

所有 GC 算法的前提是 "识别垃圾",主流判定方式有两种:

1. 引用计数法(Reference Counting)

  • 原理:为每个对象维护一个引用计数器,有引用指向时 + 1,引用失效时 - 1;计数器为 0 则判定为垃圾。
  • 优点:实时性高,回收无延迟;实现简单。
  • 致命缺陷 :无法解决循环引用(如 A 引用 B、B 引用 A,两者计数器均不为 0,但均无外部引用),JVM 未采用此方式(Python 等语言使用但需额外处理循环引用)。

2. 可达性分析算法(Reachability Analysis)

  • 原理 :以 "GC Roots" 为起点,遍历对象引用链,不可达的对象判定为垃圾。
  • GC Roots 核心类型
    • 虚拟机栈(栈帧局部变量表)中引用的对象;
    • 方法区中类静态属性引用的对象;
    • 方法区中常量引用的对象;
    • 本地方法栈(JNI)中引用的对象;
    • JVM 内部的核心对象(如锁对象、系统类加载器)。
  • 优势:解决循环引用问题,是 JVM(HotSpot)的核心判定方式。
  • 注意:可达性分析需 "暂停所有用户线程"(STW,Stop The World),否则引用链会动态变化,导致判定错误。

二、核心垃圾回收算法(基础层)

基于可达性分析,JVM 衍生出 4 类核心回收算法,各有适用场景和性能特点:

1. 标记 - 清除算法(Mark-Sweep)

原理(两步走)
  • 标记阶段:从 GC Roots 出发,标记所有可达对象;
  • 清除阶段:遍历堆内存,回收所有未标记的对象,释放内存空间。
优缺点
优点 缺点
实现简单,无需移动对象 1. 内存碎片化严重(回收后产生大量不连续的内存碎片,大对象无法分配时触发 Full GC);2. 效率低(标记 + 清除均需遍历全堆,STW 时间长);3. 仅回收空间,未整理空间。
适用场景

仅适用于老年代(对象存活率高,移动成本高),但因碎片化问题,极少单独使用(早期 CMS 的初始标记 + 并发清除阶段基于此)。

2. 标记 - 复制算法(Mark-Copy)

原理(空间换时间)
  • 将堆内存划分为大小相等的两块(From 区 / To 区),仅使用其中一块;
  • 标记阶段:标记 From 区中可达对象;
  • 复制阶段:将所有标记的对象复制到 To 区,按内存地址连续排列;
  • 交换阶段:清空 From 区,交换 From/To 区角色,下次 GC 操作新的 From 区。
优化版(Appel 式分配)

HotSpot 针对新生代优化:将新生代分为 1 个 Eden 区 + 2 个 Survivor 区(默认比例 8:1:1),而非等大分区:

  • 大部分对象在 Eden 区创建,GC 时标记 Eden+Survivor(From)的可达对象,复制到 Survivor(To);
  • 若 To 区空间不足,直接晋升到老年代;
  • 交换 From/To 区,重复此过程。
优缺点
优点 缺点
1. 无内存碎片(复制后对象连续排列);2. 回收效率高(仅处理存活对象,新生代存活率低);3. 分配内存时仅需移动指针,效率极高 1. 内存利用率低(需预留 To 区,Appel 式优化后仍损失 10%);2. 老年代存活率高,复制成本极高,不适用。
适用场景

新生代核心算法(如 Serial GC、ParNew GC、G1 的新生代回收),因新生代对象 "朝生夕死",复制成本远低于标记 - 清除。

3. 标记 - 整理算法(Mark-Compact)

原理(标记 + 移动 + 清除)
  • 标记阶段:同标记 - 清除,标记可达对象;
  • 整理阶段 :将所有标记的对象向内存一端压缩移动,保证连续排列;
  • 清除阶段:清理边界外的所有内存空间。
优缺点
优点 缺点
1. 无内存碎片;2. 内存利用率 100%(无需预留空间);3. 适合存活率高的区域。 1. 移动对象成本高(需更新所有引用指向,STW 时间长);2. 整理过程需暂停所有用户线程,并发难度大。
适用场景

老年代核心算法(如 Serial Old、Parallel Old,G1 的老年代回收也包含整理逻辑),平衡了碎片化和内存利用率。

4. 分代收集算法(Generational Collection)

本质

并非独立算法,而是基于对象生命周期的分代策略,结合上述 3 种算法的优势:

  • 新生代:对象存活率低 → 标记 - 复制算法;
  • 老年代:对象存活率高、体积大 → 标记 - 清除 / 标记 - 整理算法;
  • 永久代 / 元空间:极少回收(仅卸载无用类)。
核心优势
  • 针对不同区域选择最优算法,降低整体 GC 开销;
  • 新生代 Minor GC 频率高但耗时短,老年代 Major GC/Full GC 频率低但耗时长。
实现细节
  • 新生代 GC(Minor GC):触发条件为 Eden 区满,回收 Eden+From Survivor,存活对象移至 To Survivor;
  • 老年代 GC(Major GC):触发条件为老年代空间不足 / 晋升对象超过老年代剩余空间,或显式调用 System.gc ();
  • 空间分配担保:Minor GC 前,JVM 检查老年代最大可用连续空间是否≥新生代所有对象总大小,若不足则触发 Full GC。

三、进阶算法:并发回收与区域化回收

随着 JVM 发展,基础算法无法满足高并发、低延迟需求,衍生出并发回收区域化回收的进阶设计:

1. 并发标记 - 清除(CMS,Concurrent Mark Sweep)

核心目标

降低老年代 GC 的 STW 时间,基于 "标记 - 清除" 做并发优化,分为 4 个阶段:

  1. 初始标记(STW):标记 GC Roots 直接关联的对象,耗时极短;
  2. 并发标记:遍历引用链,标记所有可达对象(与用户线程并发执行,无 STW);
  3. 重新标记(STW):修正并发标记期间因用户线程操作导致的标记遗漏(耗时短于初始标记);
  4. 并发清除:回收未标记对象(与用户线程并发执行,无 STW)。
优缺点
优点 缺点
1. 并发阶段无 STW,低延迟(适合响应时间敏感的场景);2. 老年代回收效率高。 1. 产生内存碎片(标记 - 清除的固有问题);2. 并发阶段占用 CPU 资源,影响应用吞吐量;3. 无法处理 "浮动垃圾"(并发清除阶段产生的新垃圾,需下次 GC 回收);4. 依赖标记 - 整理做碎片整理(CMS 失败时触发 Serial Old 的 Full GC,STW 极长)。

2. 垃圾优先(G1,Garbage-First)

核心设计

打破分代边界,将堆划分为多个大小相等的 Region(默认 1~32MB),每个 Region 可动态标记为 Eden、Survivor、Old、Humongous(存储大对象),结合 "标记 - 复制" 和 "标记 - 整理",核心是 "优先回收垃圾最多的 Region"。

核心流程(简化)
  1. 初始标记(STW):标记 GC Roots 关联的对象,暂停时间极短;
  2. 并发标记:遍历引用链,标记可达对象,计算每个 Region 的垃圾占比;
  3. 最终标记(STW):修正并发标记的遗漏,同时清理 Remembered Set(记录跨 Region 引用);
  4. 筛选回收(STW):按垃圾占比排序 Region,优先回收垃圾多的 Region(新生代 Region 用标记 - 复制,老年代 Region 用标记 - 整理)。
优势
  • 可预测的 STW 时间(通过参数设置最大暂停时间);
  • 无内存碎片(整理 + 复制);
  • 兼顾吞吐量和延迟,适合大内存场景(如 16GB 以上堆)。

3. ZGC/Shenandoah(超低延迟 GC)

核心创新

基于 "染色指针" 和 "并发整理",实现几乎全程无 STW 的回收:

  • 染色指针:将对象的 GC 状态(标记、移动等)存储在指针的高位比特位,无需修改对象本身;
  • 并发移动:在用户线程访问对象时,通过指针转发机制动态更新引用,实现对象移动与用户线程并发;
  • Region 化管理:与 G1 类似,但 Region 大小可动态调整,支持 TB 级堆内存。
核心优势

STW 时间稳定在毫秒级(甚至微秒级),适合超大规模内存、超低延迟场景(如金融、实时交易)。

四、算法对比与选型建议

算法 / 收集器 核心回收算法 适用区域 STW 特点 适用场景
Serial GC 新生代:标记 - 复制;老年代:标记 - 整理 新生代 + 老年代 全程 STW,单线程 单核心、小堆内存(如客户端程序)
Parallel GC 新生代:标记 - 复制;老年代:标记 - 整理 新生代 + 老年代 全程 STW,多线程 吞吐量优先(如后台批处理)
CMS 老年代:并发标记 - 清除;新生代:ParNew(标记 - 复制) 老年代 + 新生代 仅初始 / 重新标记 STW 延迟优先(如 Web 应用)
G1 区域化:标记 - 复制 + 标记 - 整理 全堆(Region) 可预测的短 STW 大内存、兼顾吞吐量与延迟(如中间件)
ZGC/Shenandoah 区域化:并发标记 + 并发整理 全堆(Region) 几乎无 STW 超大内存、超低延迟(如金融、云计算)

五、关键优化点

  1. 卡表(Card Table):避免全堆扫描跨代引用(新生代对象引用老年代 / 老年代引用新生代),仅扫描脏卡,降低 GC 开销;
  2. Remembered Set:G1/ZGC 中记录跨 Region 引用,减少标记范围;
  3. TLAB(Thread Local Allocation Buffer):线程私有内存分配缓冲区,避免多线程分配内存竞争,提升新生代分配效率;
  4. 并发失败处理:CMS/G1 在并发阶段内存不足时,降级为 Serial Old 的 Full GC,需监控此类场景。

总结

JVM GC 算法的演进核心是平衡 "延迟(STW 时间)"、"吞吐量" 和 "内存利用率"

  • 基础算法(标记 - 清除 / 复制 / 整理)解决 "怎么回收" 的问题;
  • 分代策略解决 "按对象生命周期优化回收" 的问题;
  • G1/ZGC 等进阶算法解决 "大内存、低延迟" 的问题。

实际应用中,需根据业务场景(吞吐量 / 延迟优先)、堆内存大小、CPU 核心数选择合适的收集器,同时通过监控 GC 日志(如 GC 停顿时间、频率、碎片率)调优,避免 Full GC 频繁触发。

相关推荐
_w_z_j_5 小时前
全排列问题(包含重复数字与不可包含重复数字)
数据结构·算法·leetcode
@小码农5 小时前
LMCC大模型认证 青少年组 第一轮模拟样题
数据结构·人工智能·算法·蓝桥杯
dragoooon345 小时前
[hot100 NO.13~18]
算法
WangLanguager5 小时前
Prototypical Networks 在图像识别中表现如何?
算法
我是你们的明哥5 小时前
A*(A-Star)算法详解:智能路径规划的核心技术
后端·算法
我是你们的明哥5 小时前
从 N 个商品中找出总价最小的 K 个方案
后端·算法
民乐团扒谱机5 小时前
【微实验】谱聚类之大规模数据应用——Nyström 方法
人工智能·算法·机器学习·matlab·数据挖掘·聚类·谱聚类
CoderYanger5 小时前
A.每日一题——3606. 优惠券校验器
java·开发语言·数据结构·算法·leetcode
CoderYanger5 小时前
D.二分查找-基础——744. 寻找比目标字母大的最小字母
java·开发语言·数据结构·算法·leetcode·职场和发展