【垃圾回收基石】图解垃圾回收算法:标记-清除、复制、标记-整理、分代收集

【垃圾回收基石】图解垃圾回收算法:标记-清除、复制、标记-整理、分代收集

想象一下,你住在一个神奇的房间里:你可以随时变出各种物品(创建对象),但从不需要亲自打扫卫生。不过,这个房间有个神秘的清洁工(Garbage
Collector),他会在你不注意的时候,悄悄清理掉你不再需要的物品。今天,就让我们揭开这位清洁工的工作手册,看看他清理房间的几种核心策略。

一、为什么需要垃圾回收?

在开始之前,让我们明确一个基本概念:Java中的对象绝大部分都创建在堆内存上。但内存是有限的,如果只创建不清理,再大的内存也会耗尽。

java
public class MemoryLeakExample {
public static void main(String[] args) {
// 持续创建对象,但不释放引用
List list = new ArrayList<>();
while (true) {
list.add(new Object()); // 最终会导致OutOfMemoryError
}
}
}
垃圾回收(Garbage Collection, GC)就是自动管理堆内存,回收已经"死亡"的对象所占用的空间。那么,GC如何判断对象是否"死亡"呢?

二、垃圾识别的基石:可达性分析算法

GC并不是通过"猜"来找出垃圾的,而是通过一种称为可达性分析(Reachability Analysis) 的算法:

  • 从一组称为"GC Roots"的对象作为起点

  • 从这些 roots 开始向下搜索,走过的路径称为"引用链"

  • 如果一个对象与 GC Roots 之间没有任何引用链相连,则证明此对象不可用

GC Roots 包括

  • 虚拟机栈中引用的对象

  • 方法区中类静态属性引用的对象

  • 方法区中常量引用的对象

  • 本地方法栈中JNI引用的对象

三、四大垃圾收集算法详解

了解了如何识别垃圾后,我们来看看清洁工实际清理房间的四种策略。

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

生活类比:你在房间里贴便签标记要扔的东西,然后一次性清理掉。

工作原理

  • 标记阶段:首先遍历所有对象,标记出所有需要回收的对象

  • 清除阶段:统一回收所有被标记的对象

可视化过程

初始状态: [A(活), B(垃圾), C(活), D(垃圾), E(活), F(垃圾)]

标记阶段: [A(活), B(标记), C(活), D(标记), E(活), F(标记)]
         ↑ GC遍历并标记垃圾对象

清除阶段: [A(活), 空闲, C(活), 空闲, E(活), 空闲]
         ↑ 清除被标记的对象,留下内存碎片

优点

  • 算法简单,实现容易

  • 与复制算法相比,不需要预留一半内存

缺点

  • 效率问题:标记和清除两个过程的效率都不高

  • 空间问题:会产生大量不连续的内存碎片,导致以后分配大对象时失败,从而提前触发另一次GC

2. 复制算法(Copying)

生活类比:你把房间分成两半,只使用其中一半。当需要打扫时,把所有需要保留的东西搬到另一半房间,然后彻底清空当前这半房间。

工作原理

  • 将可用内存按容量划分为大小相等的两块

  • 每次只使用其中的一块

  • 当这一块的内存用完了,就将还存活着的对象复制到另外一块上面

  • 然后再把已使用过的内存空间一次清理掉

可视化过程

内存分为From区和To区:

From区: [A(活), B(垃圾), C(活), D(垃圾)]
To区: [空闲, 空闲, 空闲, 空闲]

复制存活对象到To区:
From区: [A(活), B(垃圾), C(活), D(垃圾)]
To区: [A(活), C(活), 空闲, 空闲]

清空From区:
From区: [空闲, 空闲, 空闲, 空闲]
To区: [A(活), C(活), 空闲, 空闲]

优点

  • 实现简单,运行高效

  • 没有内存碎片的问题

缺点

  • 内存缩小为原来的一半,浪费太多空间

  • 在对象存活率较高时,复制操作效率会变低

应用场景:商业虚拟机中,复制算法主要用于新生代的垃圾回收,因为新生代中98%的对象都是"朝生夕死"的。

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

生活类比:你不仅标记要扔的东西,还会把保留的东西整齐地推到一边,让空闲空间连续。

工作原理

  • 标记阶段:与"标记-清除"算法一样,首先标记出所有需要回收的对象

  • 整理阶段:让所有存活的对象都向一端移动

  • 清理阶段:直接清理掉端边界以外的内存

可视化过程

初始状态: [A(活), B(垃圾), C(活), D(垃圾), E(活), F(垃圾)]

标记阶段: [A(活), B(标记), C(活), D(标记), E(活), F(标记)]

整理阶段: [A(活), C(活), E(活), 空闲, 空闲, 空闲]
         ↑ 存活对象向一端移动,保持紧凑排列

清理阶段: [A(活), C(活), E(活), 空闲, 空闲, 空闲]
         ↑ 清理后得到连续的空闲空间

优点

  • 避免了内存碎片问题

  • 不需要浪费一半内存空间

缺点

  • 整理阶段涉及大量对象移动,效率较低

  • 需要暂停用户程序(Stop The World)

应用场景:主要用于老年代的垃圾回收,因为老年代中对象存活率高,不适合复制算法。

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

生活类比:你把房间分成两个区域:常用区(放经常更换的小物品)和储藏区(放长期保存的大件物品)。对这两个区域采用不同的清洁策略。

工作原理
现代商业虚拟机都采用分代收集算法,根据对象存活周期的不同将内存划分为几块:

新生代(Young Generation):存放生命周期短的对象

  • 使用复制算法(因为存活对象少,复制成本低)

  • 分为Eden空间、From Survivor空间、To Survivor空间(比例通常是8:1:1)

老年代(Tenured Generation):存放生命周期长的对象

  • 使用标记-整理或标记-清除算法

  • 永久代/元空间(PermGen/Metaspace):存放类信息、常量等(JDK8以后是元空间)

对象晋升流程

1.新对象首先分配在Eden区

2.当Eden区满时,触发Minor GC,存活对象被复制到Survivor区

3.对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁

4.当年龄达到一定阈值(默认15),就会被晋升到老年代

5.当老年代空间不足时,触发Full GC

四、算法对比与总结

算法优点缺点适用场景
标记-清除实现简单,不浪费内存效率低,产生碎片一般不单独使用
复制效率高,无碎片浪费一半内存新生代(对象存活率低)
标记-整理无碎片,不浪费内存效率较低老年代(对象存活率高)
分代收集综合优势,实际应用实现复杂现代商业虚拟机

五、现实世界中的GC

实际上,现代的垃圾收集器(如Serial、Parallel、CMS、G1、ZGC等)都是基于这些基础算法的组合和优化。例如:

  • G1收集器:将堆划分为多个Region,采用复制算法进行局部收集

  • ZGC收集器:使用读屏障和颜色指针等技术,极大减少了STW时间

  • 理解这些基础算法,是理解复杂垃圾收集器工作原理的关键。

下一篇预告

现在你已经了解了GC如何工作的基本原理,但如何监控GC活动?如何分析GC日志?如何根据实际情况选择合适的垃圾收集器?

下一篇【GC日志分析】,我们将带你实战解析GC日志,让你真正具备诊断和调优GC问题的能力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值