← 返回题目列表

常见的垃圾回收算法有哪些?

高频 中等 第 1 / 34 题 更新于 2026/07/25
垃圾回收GC算法分代收集

简化版

三种基础算法:标记-清除(标出垃圾直接清,会产生内存碎片)、复制(经典半区模型把存活对象复制到另一块空间,能获得连续空间但需要预留复制目标)、标记-整理(标记后把存活对象向一端挪、清掉边界外,无碎片但慢)。实际的分代收集把它们组合用:年轻代用复制(存活少、复制快),老年代用标记-清除或标记-整理(存活多、不能浪费一半空间)。

详细版

① 标记-清除(Mark-Sweep) 分两步:先标记所有存活对象(从 GC Root 可达性分析),再清除没标记的。

  • 优点:简单,不用移动对象。
  • 缺点:产生大量内存碎片(清掉的对象散落各处),后续大对象可能因没有连续空间而提前触发 GC;且标记、清除两阶段效率都不高。

② 复制(Copying) 把内存分成两半,只用一半。GC 时把存活对象复制到另一半,然后把原来那半整个清空。

  • 优点:无碎片(复制后紧凑排列)、分配快(只需移动指针)。
  • 缺点:内存利用率只有一半,浪费严重;存活对象多时复制成本高。

③ 标记-整理(Mark-Compact) 标记阶段同标记-清除,但之后不是直接清,而是把所有存活对象向内存一端移动,再清掉边界外的所有空间。

  • 优点:无碎片,且不浪费一半内存。
  • 缺点:要移动对象、更新引用,性能开销大,且移动时通常要 STW。

完整版教学

一、一切的起点:先判断谁是垃圾

三种算法都先要回答「哪些对象是垃圾」。JVM 用可达性分析:从 GC Roots(栈里的局部变量、静态变量、常量等)出发,能引用到的对象是存活的,引用不到的就是垃圾(详见「JVM 如何判断对象可回收」那道题)。标记算法标的就是这些「可达的存活对象」。例如对象图共有 1,000 个对象,从根只能走到 120 个,那么其余 880 个才是回收候选;互相引用但不连到根的环也属于候选。这里得到的是逻辑存活集合,空间何时重用还取决于后续清除、复制或整理阶段。

二、三种算法的取舍本质是「碎片 vs 空间 vs 速度」

把三者放一起对比,就能看清它们是在三个维度上做权衡:

算法碎片空间利用率是否移动对象适合
标记-清除有碎片较高并发清扫、组合收集阶段
复制无碎片需预留目标空间存活对象少的区域
标记-整理✅ 无碎片✅ 高存活对象多的区域

没有一种算法全能:标记-清除省空间但有碎片,复制无碎片但费空间,标记-整理两者兼顾但慢。所以现代 JVM 不单用某一种,而是分代组合

三、分代收集:为什么年轻代用复制、老年代用标整

这是这道题的落脚点。基于「大多数对象朝生夕灭」的假设,堆分年轻代和老年代,各用最适合的算法:

  • 年轻代:对象绝大多数很快就死,存活的极少。复制算法只需复制这一小撮存活对象,成本极低,无碎片——完美契合。因此传统分代收集器常在年轻代采用复制。Eden 与两个 Survivor 的常见初始经验值是 8:1:1,但比例可配置或被自适应策略调整,不能把“只浪费 10%”当作固定结论。
  • 老年代:对象存活率高、数量大。用复制算法要复制一大堆存活对象、还得留一半空间,太亏。所以老年代用标记-清除标记-整理(如 CMS 常规周期以标记-清除为主,Serial Old / Parallel Old 采用标记-整理)。

这就是「复制算法配存活少的年轻代、标记整理配存活多的老年代」的道理——按存活率选算法

四、算法和收集器的关系

要区分「算法」和「收集器」:算法是理论方法,收集器是具体实现。比如:

  • Serial / ParNew / Parallel Scavenge(年轻代)→ 复制算法;
  • Serial Old / Parallel Old(老年代)→ 标记-整理;
  • CMS(老年代)→ 标记-清除(所以它有碎片问题);
  • G1 → 整体看是标记-整理(Region 之间复制),避免碎片。

(收集器细节见「JVM 有哪几种垃圾回收器」那道题。)

五、并发标记为什么需要写屏障

并发收集器允许业务线程在标记期间继续改引用,单靠三色标记可能漏掉仍存活的对象:

白色:尚未发现
灰色:已发现,但子引用未扫描完
黑色:对象及其当时的子引用已扫描完

若黑对象删除对白对象的引用,同时灰对象又新增对白对象的引用却未被记录,收集器就可能错误回收白对象。实现会借助写屏障记录引用变化,并采用 SATB 或增量更新等策略维持正确性。

策略思路重点记录典型理解
SATB并发标记开始时的旧引用关系保存被覆盖的旧引用,近似处理“开始时存活”
增量更新黑对象新指向的白对象重新关注被修改的黑对象或新引用

这些机制会产生额外 CPU 和内存开销,所以“并发 GC”不是免费地消除暂停,而是在暂停、吞吐和额外屏障成本之间交换。

六、怎样按存活率估算算法成本

假设某区域容量 1 GB,回收时只有 50 MB 存活:复制存活对象约处理 50 MB,并可一次获得连续空间;若有 900 MB 存活,复制成本就明显升高。

复制量近似 ∝ 存活对象大小
可回收量 = 区域已用量 - 存活量

标记-清除不移动存活对象,但空闲块可能零散;标记-整理需要移动对象和修正引用,却能得到连续空间。实际收集器通常组合多种算法,Region 化收集器还会选择部分区域做疏散,不能简单贴一个算法标签概括全部阶段。

选算法时真正要问的是对象存活率、是否允许碎片、可用额外空间、暂停目标以及并发修改成本。

七、常见误区与追问

  • 误区:复制算法固定浪费一半堆空间。 半区复制只是经典模型,实际年轻代比例和 Region 疏散策略由收集器决定。
  • 误区:标记-清除完成后一定能分配任意大小对象。 总空闲量足够时仍可能因碎片缺少连续空间。
  • 误区:并发标记期间业务线程完全不受影响。 写屏障、并发线程抢占 CPU 和最终处理阶段都会带来成本。
  • 追问:标记-整理和复制算法有什么共同点? 两者都会移动存活对象并更新引用,区别主要在目标空间组织和使用场景。
  • 追问:为什么年轻代常用复制或疏散? 年轻代通常存活率低,复制少量存活对象能快速回收大片连续空间。
  • 追问:CMS 为什么容易产生碎片? 它的老年代主要采用并发标记清除,不在常规清除阶段持续压缩所有存活对象。
  • 追问:三色标记本身能解决并发修改吗? 不能,还需要写屏障和 SATB、增量更新等并发标记协议。

八、加强记忆

把三种基础算法记成“清会碎、复制看存活、整理换连续”:标记-清除少移动但留下碎片,复制只搬存活对象却需要目标空间,标记-整理用移动与引用修正换取连续内存。分代和 Region 化不是第四种孤立算法,而是依据存活率把这些方法组合到不同区域。并发标记还必须用写屏障配合 SATB 或增量更新,避免业务线程改引用造成漏标。答题时始终围绕存活率、额外空间、碎片、暂停和吞吐五个代价展开。