你知道哪些垃圾收集算法
垃圾收集算法是 JVM 回收堆内存中死亡对象的核心策略,不同算法适用于不同的内存区域(新生代、老年代),核心目标是 “高效回收内存、减少 STW 时间”,常见算法如下:
# 1. 标记 - 清除算法(Mark-Sweep)
- 核心流程:分为 “标记” 和 “清除” 两步:
- 标记:通过可达性分析,标记所有存活对象;
- 清除:遍历内存区域,清除未被标记的死亡对象,释放内存。
- 适用场景:老年代(对象存活时间长,死亡对象少,标记和清除效率高);
- 优点:实现简单,无需移动对象;
- 缺点:
- 产生内存碎片:清除后内存中会出现大量不连续的空闲块,当需要分配大对象时,可能因找不到足够大的连续空闲块而触发 FGC;
- 效率低:需遍历两次内存(标记一次、清除一次),当内存区域大、对象多时,效率低。
- 典型收集器:CMS 收集器(老年代)。
# 2. 复制算法(Copying)
- 核心流程:将内存区域分为两个大小相等的空间(如新生代的 From Survivor 和 To Survivor):
- 标记:标记存活对象;
- 复制:将存活对象复制到另一个空闲空间(如 From Survivor 的存活对象复制到 To Survivor);
- 清空:清空原空间的所有对象(死亡对象和存活对象已复制)。
- 适用场景:新生代(对象朝生夕死,存活对象少,复制成本低);
- 优点:
- 无内存碎片:复制后空闲空间是连续的,便于大对象分配;
- 效率高:只需复制存活对象,且清空原空间时无需遍历,直接覆盖;
- 缺点:
- 内存利用率低:需将内存分为两个相等空间,总有一个空间空闲(如新生代的 Survivor 区,利用率仅 50%);
- 不适合存活对象多的区域:若存活对象多(如老年代),复制成本高,效率低。
- 典型收集器:Serial 收集器(新生代)、ParNew 收集器(新生代)。
# 3. 标记 - 整理算法(Mark-Compact)
- 核心流程:在 “标记 - 清除” 算法基础上增加 “整理” 步骤,解决内存碎片问题:
- 标记:标记所有存活对象;
- 整理:将存活对象向内存一端移动,使存活对象紧凑排列;
- 清除:清空内存另一端的死亡对象,释放连续内存。
- 适用场景:老年代(存活对象多,需避免内存碎片);
- 优点:
- 无内存碎片:存活对象紧凑排列,空闲空间连续;
- 内存利用率高:无需划分两个相等空间,内存全部可用;
- 缺点:
- STW 时间长:整理步骤需要移动对象,且需更新对象的引用地址(如 A 引用 B,B 移动后 A 的引用需指向新地址),操作复杂,耗时久;
- 效率低于复制算法:比复制算法多了 “整理” 步骤。
- 典型收集器:Serial Old 收集器(老年代)、Parallel Old 收集器(老年代)。
# 4. 分代收集算法(Generational Collection)
- 核心思想:基于 “大部分对象朝生夕死” 和 “存活对象逐渐晋升老年代” 的规律,将堆内存分为新生代和老年代,不同代采用不同 GC 算法:
- 新生代:用复制算法(存活对象少,复制成本低);
- 老年代:用标记 - 清除算法或标记 - 整理算法(存活对象多,避免内存碎片);
- 优点:结合不同算法的优势,提升整体 GC 效率;
- 缺点:依赖分代模型,若对象存活周期不符合 “朝生夕死”(如大量长存活对象),效率会下降;
- 典型收集器:所有主流收集器(如 CMS、G1、ZGC)均基于分代收集思想(G1、ZGC 支持部分代回收)。
# 5. 分区收集算法(Region-Based Collection)
- 核心思想:将堆内存分为多个大小相等的 “Region”(区域,如 G1 的 Region 大小为 1-32MB),每个 Region 可独立进行 GC,无需回收整个堆:
- 每个 Region 可标记为新生代(Eden/Survivor)或老年代,动态调整;
- GC 时,优先回收垃圾比例高的 Region(“Garbage First”,如 G1 收集器),减少 STW 时间;
- 适用场景:大堆内存(如堆内存 10GB 以上),避免全堆 GC 导致的长时间 STW;
- 优点:
- 支持部分代回收:无需回收整个堆,STW 时间可控;
- 灵活适配对象分布:Region 的代属性可动态调整,适合复杂对象存活场景;
- 缺点:
- 实现复杂:需管理多个 Region 的状态、引用关系(如跨 Region 引用需用 Remembered Set 记录);
- 内存开销大:需维护 Remembered Set 等额外数据结构。
- 典型收集器:G1 收集器、ZGC 收集器、Shenandoah 收集器。
# 6. 增量收集算法(Incremental Collection)
- 核心思想:将 GC 过程拆分为多个小步骤,与用户线程交替执行,减少单次 STW 时间(如将 “标记” 步骤拆分为 10 次,每次执行 10%,然后切换到用户线程);
- 适用场景:早期 JVM(如 JDK 1.2 之前),用于低配置设备(如手机),避免长时间 STW 影响用户体验;
- 优点:单次 STW 时间短,用户线程响应性好;
- 缺点:
- 总 GC 时间长:GC 步骤拆分后,上下文切换成本高,整体 GC 耗时增加;
- 可能导致 “浮动垃圾”:GC 步骤间隙,用户线程可能产生新的死亡对象,需下次 GC 回收;
- 现状:已被并发收集算法替代(如 CMS、G1),现代 JVM 很少使用。
# 7. 并发收集算法(Concurrent Collection)
- 核心思想:GC 的大部分步骤(如标记、清除)与用户线程并发执行,仅在关键步骤(如初始标记、重新标记)暂停用户线程(STW),大幅减少 STW 时间;
- 适用场景:高并发应用(如电商、金融),对 STW 时间敏感;
- 优点:STW 时间短(毫秒级),用户线程影响小;
- 缺点:
- 实现复杂:需处理 GC 线程与用户线程的并发冲突(如对象引用变动),需维护额外数据结构(如 CMS 的 Card Table、G1 的 Remembered Set);
- 可能产生 “浮动垃圾”:并发执行时,用户线程产生的新死亡对象需下次 GC 回收;
- 内存占用高:额外数据结构(如 Card Table)占用内存;
- 典型收集器:CMS 收集器、G1 收集器、ZGC 收集器。
上次更新: 12/30/2025