标记-清除算法分标记和清除两阶段:先从gc roots出发标记存活对象,再回收未标记对象内存并加入空闲列表;不移动对象、不整理内存、不分代,导致内存碎片化。

标记-清除算法通过两个明确阶段执行:先识别哪些对象还“活着”,再回收其余空间,不移动对象,也不整理内存。
标记阶段:从根出发,遍历并标记存活对象
垃圾收集器以 GC Roots(如栈帧中的局部变量、静态字段、JNI 引用等)为起点,使用可达性分析递归扫描所有能被访问到的对象,并在对象头(Object Header)中设置标记位。这个过程确保只有真正被引用的对象被保留下来。
- 标记范围覆盖整个堆内存,包括新生代和老年代(取决于触发的是 Minor GC 还是 Full GC)
- 未被标记的对象即判定为垃圾,但此时内存尚未释放
- 该阶段必须暂停应用线程(Stop-The-World),否则引用关系可能动态变化导致漏标
清除阶段:遍历堆,回收未标记对象的空间
清除不是直接抹除数据,而是将未被标记的对象所占内存块加入空闲列表(Free List),供后续 new 对象时分配使用。原对象的内存区域保持原状,仅逻辑上变为“可复用”。
- 清除过程同样需要遍历整个堆,效率随堆大小线性下降
- 被回收的内存块位置随机,彼此不连续 → 形成大量内存碎片
- 后续分配大对象时,即使总空闲内存足够,也可能因无连续空间而提前触发 GC 或直接 OOM
它不做什么,这点很关键
标记-清除不做以下三件事,这正是它区别于其他算法的核心特征:
- 不移动任何存活对象(所以对象地址不变,无需更新引用)
- 不清除后压缩或整理内存(因此碎片无法自动修复)
- 不区分对象年龄或生命周期(不像分代收集那样做针对性优化)
实际运行中的典型表现
比如一次 Full GC 使用标记-清除清理老年代后,监控工具中常看到:已用内存明显下降,但“最大连续空闲空间”远小于“总空闲空间”——这就是碎片化的直观体现。若之后频繁分配中等大小对象(如 2MB 缓冲区),就容易因找不到连续块而反复触发 GC,拖慢吞吐。











