垃圾回收算法

JVM垃圾回收算法(GC)的核心目标是自动识别并回收不在被引用的对象内存,避免内存泄漏或者溢出,底层依赖多种垃圾回收算法,不同算法在识别垃圾,回收内存,性能开销上各有侧重,实际GC器比如G1,ZGC都是基于这些基础的组合优化

一、前置:如何判断对象是垃圾?

JVM如何判定一个对象是否该被回收?核心标准是对象是否可达

  • 以GC Roots为起点(如果虚拟机站中局部变量,方法区中类静态变量,本地方发展中JNI引用等),遍历对象引用链
  • 如果一个对象到GC Roots没有任何可达路径及时不可达,就标记为垃圾对象,等待回收

二、核心垃圾回收算法

1.标记-清除算法(Mark-Sweep):最基础的算法

原理

分为两步执行:

  1. 标记阶段:遍历所有对象,标记出可达的存活对象(未标记的就是视为垃圾)
  2. 清除阶段:遍历堆内存,直接回收所有未标记的垃圾对象,释放内存空间

示意图

复制代码
标记前:[存活A][垃圾B][存活C][垃圾D][存活E]
标记后:[存活A(标记)][垃圾B(未标)][存活C(标记)][垃圾D(未标)][存活E(标记)]
清除后:[存活A][空闲][存活C][空闲][存活E]

优缺点

  • 优点:实现简单,不需要移动对象,执行效率高(标记和清除过程直接)
  • 缺点:
  1. 内存碎片:回收产生大量不连续的空闲内存块,如果需要分配大对象,可能会因为没有足够的内存空间触发 Full GC;
  2. 效率不稳定:堆中的对象越多,标记和清除的遍历时间就越长,GC停顿时间肯呢个随着堆大小增长而增加

适用场景

早起GC器(比如Serial GC)的老年代实现,对内存连续性要求不高,对象存活率低的场景

2.复制算法(Copying):解决碎片问题的高效方案

原理

基于存活对象少,垃圾多的场景优化,核心就是分块复制和移动对象

  1. 将堆内存划分为两个大小相等的区域,每次只使用其中一块区域
  2. 标记阶段:标记From区中所有存活对象
  3. 复制阶段:将所有存活对象赋值到另一个空闲区域,在复制后对象在To区中是连续排列的
  4. 切换阶段:清空From区,将From区和To区角色互换一下(下次GC的时候使用新的From区)

示意图

复制代码
初始状态:From区[存活A][垃圾B][存活C][垃圾D],To区[空闲][空闲][空闲][空闲]
标记+复制后:To区[存活A][存活C][空闲][空闲],From区[空闲][空闲][空闲][空闲]
角色互换:新From区[存活A][存活C][空闲][空闲],新To区[空闲][空闲][空闲][空闲]

优缺点

  • 优点
  1. 无内存碎片:存活对象复制后连续排列,后续分配大对象时效率高;
  2. 回收效率高:只复制存活对象,若存活对象占比低(如新生代 90% 以上是临时对象),复制开销极小;
  • 缺点
  1. 内存利用率低:堆内存被划分为两半,实际可用内存仅为总堆的 50%;
  2. 不适合存活对象多的场景:若存活对象占比高(如老年代),复制开销会急剧增加。

适用场景

新生代 GC(如 Serial GC、Parallel Scavenge GC),因为新生代对象生命周期短、存活占比低,契合复制算法的优势。

  1. 标记 - 整理算法(Mark-Compact):兼顾连续与利用率

原理

结合 "标记 - 清除" 和 "复制" 的优点,解决老年代存活对象多的问题,分三步:

  1. 标记阶段:与标记 - 清除一致,标记所有可达的存活对象;
  2. 整理阶段:将所有存活对象向堆内存的一端移动,紧凑排列;
  3. 清除阶段:直接清理存活对象边界之外的所有垃圾内存(无需遍历所有垃圾)。

示意图

标记前:[存活A][垃圾B][存活C][垃圾D][存活E] 标记后:[存活A(标记)][垃圾B(未标)][存活C(标记)][垃圾D(未标)][存活E(标记)] 整理后:[存活A][存活C][存活E][空闲][空闲]

优缺点

  • 优点
  1. 无内存碎片:存活对象紧凑排列,内存连续性好;
  2. 内存利用率高:无需划分两半区域,全部堆内存均可使用;
  • 缺点
  1. 额外的移动开销:需要移动存活对象,并更新所有引用该对象的指针(耗时);
  2. 停顿时间更长:整理阶段比标记 - 清除的清除阶段更耗时,适合对停顿不敏感的场景。

适用场景

老年代 GC(如 Serial Old GC、Parallel Old GC),因为老年代对象生命周期长、存活占比高,复制算法效率低,标记 - 清除会产生碎片,而标记 - 整理是最优选择。

  1. 分代收集算法(Generational Collection):实际 GC 器的核心逻辑

原理

不是独立算法,而是基于 "对象生命周期不同" 的分代策略,组合上述三种基础算法:

  1. 分代划分 :将堆内存分为「新生代」和「老年代」(部分 GC 器如 G1 还有「永久代 / 元空间」,但元空间不涉及堆内存回收);
    • 新生代:存储新创建的对象,生命周期短、存活占比低(约 1%~10% 存活);
    • 老年代:存储经过多次 GC 仍存活的对象,生命周期长、存活占比高(约 90% 以上存活);
  2. 算法组合
    • 新生代:采用「复制算法」(高效、无碎片,契合低存活占比);
      • 进一步细分:Eden 区(80%)+ 两个 Survivor 区(From/To 各 10%),每次只使用 Eden 和一个 Survivor,存活对象复制到另一个 Survivor,多次存活后晋升到老年代;
    • 老年代:采用「标记 - 清除」或「标记 - 整理」(兼顾利用率和连续性,契合高存活占比);
  3. 回收触发
    • 新生代 GC(Minor GC):Eden 区满时触发,只回收新生代,停顿时间短;
    • 老年代 GC(Major GC/Full GC):老年代满或元空间不足时触发,回收新生代 + 老年代,停顿时间长。

优缺点

  • 优点:针对性优化,兼顾效率和内存利用率,是目前所有主流 GC 器(如 Parallel GC、G1、ZGC)的基础;
  • 缺点:分代逻辑复杂,需要维护对象年龄、晋升规则等,依赖 JVM 对对象生命周期的精准判断

适用场景

所有现代 JVM(HotSpot 等)的默认 GC 策略,几乎覆盖所有应用场景(从桌面应用到服务器应用)。

  1. 分区收集算法(Region-Based Collection):大堆内存的优化方案

原理

为解决 "大堆内存下 Full GC 停顿过长" 的问题(如堆内存达数十 GB),核心是「将堆划分为多个小 Region,按需回收」:

  1. 堆内存被划分为多个大小相等的 Region(如 G1 中 Region 大小为 1~32MB,可配置);
  2. 每个 Region 可动态标记为「新生代(Eden/Survivor)」或「老年代」,无需固定分代比例;
  3. GC 时优先回收「垃圾占比高的 Region」(G1 称为 "优先回收"),避免全堆扫描;
  4. 结合标记 - 整理算法:对单个 Region 内的存活对象进行整理,保证内存连续性。

优缺点

  • 优点
  1. 降低停顿时间:只回收部分 Region,而非全堆,大堆内存下停顿可控;
  2. 灵活分代:Region 角色可动态调整,适配不同对象分布场景;
  • 缺点
  1. Region 管理复杂:需要维护每个 Region 的状态(空闲、新生代、老年代、垃圾占比等);
  2. 额外开销:Region 之间的引用需要通过 "卡表"(Card Table)追踪,增加内存和计算开销。

适用场景

大堆内存场景(如堆内存 ≥ 8GB),主流 GC 器如 G1 GC、ZGC、Shenandoah GC 均基于此算法。

三、主流 GC 器与算法对应关系

GC 器 适用代际 核心算法组合 特点
Serial GC 新生代 + 老年代 新生代:复制算法;老年代:标记 - 整理 单线程,停顿长,适合小堆
Parallel Scavenge GC 新生代 复制算法 多线程吞吐量优先
Parallel Old GC 老年代 标记 - 整理 多线程吞吐量优先
CMS GC(废弃) 老年代 标记 - 清除(并发标记) 低停顿,有内存碎片
G1 GC 全堆(Region) 分区收集 + 标记 - 整理 + 复制 平衡停顿与吞吐量,大堆友好
ZGC 全堆(Region) 分区收集 + 标记 - 整理(并发整理) 超低停顿(毫秒级),超大堆支持
Shenandoah GC 全堆(Region) 分区收集 + 标记 - 整理(并发整理) 低停顿,跨平台支持

四、核心总结

  1. 基础算法定位
    • 标记 - 清除:简单但有碎片,适合临时场景;
    • 复制算法:高效无碎片但利用率低,适合低存活占比(新生代);
    • 标记 - 整理:无碎片高利用率但有移动开销,适合高存活占比(老年代);
  2. 实际应用逻辑
    • 分代收集是基础策略,结合复制 + 标记 - 整理,覆盖大多数场景;
    • 分区收集是大堆优化,通过 Region 按需回收,解决大堆停顿问题;
  3. 选择原则
    • 小堆(<4GB):Serial GC / Parallel GC(吞吐量优先);
    • 中堆(4~16GB):G1 GC(平衡停顿与吞吐量);
    • 大堆(>16GB):ZGC / Shenandoah GC(超低停顿)。
相关推荐
胖咕噜的稞达鸭1 小时前
算法入门:专题二分查找算法 模板总结 题目练手 :排序数组中查找元素的第一个和最后一个位置 第一个错误的版本 查找x的平方根 搜索插入位置 山脉数组的封顶索引
c语言·c++·算法·leetcode
松涛和鸣1 小时前
21、单向链表完整实现与核心技巧总结
linux·c语言·数据结构·算法·链表
TL滕1 小时前
从0开始学算法——第三天(数据结构的操作)
数据结构·笔记·学习·算法
Aaron15881 小时前
基于FPGA实现卷积方法比较分析
arm开发·算法·fpga开发·硬件架构·硬件工程·射频工程·基带工程
AndrewHZ1 小时前
【图像处理基石】什么是分水岭算法?
图像处理·算法·计算机视觉·图像分割·cv·形态学分割
前端小L1 小时前
回溯算法专题(五):去重与剪枝的双重奏——攻克「组合总和 II」
算法·剪枝
TL滕2 小时前
从0开始学算法——第三天(数据结构的多样性)
数据结构·笔记·学习·算法
V1ncent Chen2 小时前
人工智能的基石之一:算法
人工智能·算法
无限进步_2 小时前
深入理解顺序表:从原理到完整实现
c语言·开发语言·数据结构·c++·算法·链表·visual studio