标记-清除算法分三步:标记存活对象、清除未标记垃圾、组织空闲链表;不移动对象导致内存碎片,标记时间取决于存活对象数,清除时间取决于堆总大小,空闲链表分配常用first-fit策略。

标记-清除算法的核心流程就三步:先标记所有存活对象,再清除未标记的垃圾,最后把回收的空间组织成空闲链表供后续分配。整个过程不移动对象,实现直接,但会带来碎片问题。
标记阶段:从根出发递归标记存活对象
标记从 GC Roots(如栈帧中的局部变量、静态字段、寄存器等)开始,逐层访问引用的对象,并设置其 mark 位为 true。使用深度优先搜索(DFS)更节省栈空间,且能自然处理循环引用——遇到已标记对象直接跳过,避免重复递归。
- 伪代码中 mark(obj) 先判空再递归子对象,确保每个对象最多标记一次
- mark 位必须存在对象头中,通常用一个比特位实现,开销小
- 标记时间与活动对象数量成正比,和堆总大小无关
清除阶段:遍历堆,回收未标记内存块
清除阶段线性扫描整个堆内存,对每个对象检查 mark 位:若为 false,说明不可达,将其加入空闲链表;若为 true,则清除 mark 位(为下次 GC 复位),并保留该对象。
- 清除时间与堆总大小成正比,哪怕只有少量垃圾也要扫完整个堆
- 回收时需知道每个对象的 size 字段(也存于对象头),才能正确计算下一个对象地址
- 相邻的未标记块会在清除过程中自动合并,减少碎片
空闲链表:管理回收内存,支持快速再分配
清除后,所有回收块按单链表形式串在一起,形成 free_list。当程序申请新内存时,分配器从中查找合适大小的块。
- 常用策略是 first-fit:找到第一个 ≥ 请求大小的块就返回,兼顾速度与利用率
- best-fit 虽更省空间,但需遍历全链表,性能差;worst-fit 容易产生大量小碎片,一般不用
- 若找到的块比请求大,就分割:前段分配,后段作为新空闲块插回链表
- 实际系统常采用多级空闲链表(如按 16B/32B/64B… 分桶),大幅加速小对象分配
不复杂但容易忽略:标记-清除本身不做对象移动,所以无法缓解内存碎片;而空闲链表的查找效率直接受碎片程度影响——块越碎,分配越慢。










