🎯 一、JVM 为什么需要垃圾回收算法?
因为:
-
Java 会不停地创建对象(
new
) -
有些对象很快就没用了
-
为了避免内存爆掉,JVM 会自动清理“没用了的对象”
怎么清理?→ 就靠 GC 算法
✅ 二、JVM 的几种经典 GC 算法(按时间线 + 适用阶段)
算法名 | 属于哪代 | 特点 | 是否 STW |
---|---|---|---|
1. 标记-清除 | 老年代 | 经典算法,先标记,再清除 | 是 |
2. 标记-压缩 | 老年代 | 清完后压缩对象,防止内存碎片 | 是 |
3. 复制算法 | 新生代 | 空间换时间,效率高 | 是 |
4. 分代收集 | 整个堆 | JVM 默认使用的“混合策略” | 是(部分阶段可并发) |
5. G1 算法 | 新生代 + 老年代 | 分区 + 并发 + 可控停顿 | 是(短) |
6. ZGC/Shenandoah | 新生代 + 老年代 | 高并发,低延迟,暂停极短 | 是(极短) |
🔍 三、各个算法详解(超通俗)
① 标记-清除算法(Mark-Sweep)
📌 步骤:
-
标记:找出所有还“活着”的对象
-
清除:删除没被标记的对象
🧠 举例:
内存:A B C D E(A、C活着)
标记:✔ A ✔ C
清除:留下 A、C,其它清除
🧨 缺点:
-
清理后内存碎片多(像挖坑)
-
下次分配新对象可能找不到连续内存
② 标记-压缩算法(Mark-Compact)
📌 步骤:
-
标记活对象
-
把所有活对象压到一边
-
然后清理死对象区域
📊 图示脑补:
压前:✔A ✔C ✘B ✘D ✘E
压后:✔A ✔C [空][空][空]
✅ 优点:没有碎片!
💥 代价:压缩过程慢,适合老年代(回收不频繁)
③ 复制算法(Copying)
📌 经典应用在新生代
内存划为两个区:From 区 和 To 区
-
每次只用一块
-
GC 时,把活对象复制到另一块
-
然后直接清空当前区
✅ 优点:
-
没有碎片
-
复制速度快
🧠 举例:
From 区:A B C(A 活着)
复制到 To 区:只复制 A,然后 From 区整体清空
❗ 缺点:
-
空间浪费一半(需要两个区)
④ 分代收集算法(JVM 默认策略)
🌱 新生代(年轻对象多)→ 用“复制算法”
🌳 老年代(活得久)→ 用“标记-清除 + 标记-压缩”
📦 JVM 为什么要这么分?
因为:
-
大部分对象“很快就死”
-
少部分“活得久”
⏱ 这样可以 提升性能,让大部分对象快速被清理掉
⑤ G1(Garbage First)算法
JDK 9 之后推荐使用的 GC
📌 特点:
-
把堆划分成 N 个小块(Region)
-
每次回收“垃圾最多的区域”
-
并发执行 + 可预测停顿时间(可配置)
👍 优点:
-
高性能 + 可控延迟
-
避免老年代 Full GC 卡顿
⑥ ZGC / Shenandoah(JDK 11+ 的黑科技)
🌟 主打:
-
超大内存支持(数百 GB)
-
停顿时间低于 10ms(几乎无感知)
📦 原理:用了更复杂的染色指针和读屏障,避免全停
✅ 四、一图总结(建议记住)
算法 | 用在哪 | 特点 | 缺点 |
---|---|---|---|
标记-清除 | 老年代 | 简单、直观 | 内存碎片 |
标记-压缩 | 老年代 | 无碎片 | 慢 |
复制 | 新生代 | 快、无碎片 | 空间浪费 |
分代收集 | 整个堆 | 按生命周期优化 | 策略复杂 |
G1 | 全堆 | 分区回收、并发、可控延迟 | 启动复杂 |
ZGC | 全堆 | 几乎无 STW | 需高版本 JVM |
🎤 面试回答建议句式:
JVM 垃圾回收采用多种算法组合。新生代对象短命,用复制算法清理快;老年代对象生命周期长,用标记-清除和标记-压缩来处理。现代回收器如 G1 和 ZGC 会通过分区、并发回收等手段降低 STW 时间,适用于高并发或低延迟系统。