标记-清除算法是jvm垃圾回收最基础的实现方式,它分标记和清除两阶段:先从gc roots出发标记存活对象,再扫描堆内存释放未标记对象空间并加入空闲链表;适合老年代因存活率高、无需移动对象且堆利用率高,但存在内存碎片、stw时间长和空闲链表管理成本高三大缺陷;虽少单独使用,却作为cms、g1、zgc等回收器的基石延续至今。

标记-清除算法是JVM垃圾回收最基础的实现方式,它不移动对象,只做两件事:先标记哪些对象还“活着”,再把没被标记的内存空间回收掉。理解它,关键不在记住步骤,而在看清它的取舍——用碎片换简单,用停顿换直接。
标记-清除是怎么工作的
整个过程分两个明确阶段:
- 标记阶段:从GC Roots(如线程栈、静态变量、常量池引用等)出发,遍历所有可达对象,并在对象头中打上“存活”标记;
- 清除阶段:线性扫描整个堆内存,把未被标记的对象所占空间释放出来,加入空闲链表(Free List),供后续对象分配使用。
注意:“清除”不是把内存清零,而是把地址登记进空闲列表。下次分配时,JVM会查这个列表,找到合适大小的空闲块来复用。
它为什么适合老年代
老年代的特点是对象存活率高、死亡对象少。标记-清除在这种场景下反而有优势:
- 标记阶段只需遍历少量存活对象,开销小;
- 不需要额外预留内存空间,堆利用率接近100%;
- 不移动对象,避免了更新引用、调整指针等复杂操作,逻辑轻量。
所以现代JVM的老年代回收器(如CMS早期版本、Serial Old默认策略)仍以标记-清除为基础,或在其后叠加整理步骤来缓解缺陷。
三大硬伤:碎片、停顿、管理成本
它的缺陷不是设计疏忽,而是原理决定的必然代价:
- 内存碎片严重:存活对象原地不动,清除后空闲空间呈离散分布。当需要分配大对象(如大数组、缓存块)时,即使总空闲内存足够,也可能因找不到连续块而触发Full GC;
- 两次全堆扫描:标记和清除都需遍历整个堆。堆越大,STW(Stop-The-World)时间越长,应用响应明显卡顿;
- 空闲链表维护负担重:空闲块大小不一、位置分散,JVM分配新对象时需遍历链表查找合适块,可能引发首次适配、最佳适配等策略开销,影响吞吐。
它没有消失,只是被组合使用
纯标记-清除已很少单独出现,但它的思想深度融入主流回收器:
- CMS(Concurrent Mark-Sweep)以并发标记+并发清除为核心,目标是减少STW,但最终仍面临碎片问题,不得不依赖Fallback机制(如退化为Serial Old进行标记-整理);
- G1虽主打分区与增量回收,但在Region内清理垃圾时,底层仍采用类似标记-清除的思路,再通过Remembered Set控制跨区引用;
- ZGC和Shenandoah这类低延迟收集器,虽用读屏障+并发转移,但“识别垃圾”的起点仍是可达性分析后的标记结果,本质延续了标记阶段的逻辑。
换句话说,标记-清除不是被淘汰了,而是从主角变成了基石——它定义了“什么是垃圾”,后续所有优化,都是围绕如何更高效、更平滑地执行这两个字展开。











