标记清除算法是JavaScript引擎主流垃圾回收方式,分标记(从根对象递归标记活跃对象)和清除(释放未标记对象内存)两步;现代引擎通过分代回收、增量标记、并发标记优化性能。

标记清除算法是 JavaScript 引擎(如 V8)当前实际采用的主流垃圾回收方式。它不依赖引用计数,而是从程序“可达性”出发,分两步精准识别并释放真正无用的对象。
标记阶段:从根开始,逐层标记所有活跃对象
垃圾回收器会先锁定一组可信的起点,称为“根对象”,包括:
- 全局对象(浏览器中是 window,Node.js 中是 global)
- 当前执行栈中的局部变量、函数参数、临时上下文
- 正在被 DOM 或其他 API 持有引用的对象(如 document.getElementById() 返回的节点)
然后从这些根出发,递归遍历所有能被直接或间接访问到的对象,并把它们标记为“活跃”。这个过程常用三色标记法优化:白色(未访问)、灰色(待扫描)、黑色(已扫描完成),避免重复和遗漏。
清除阶段:回收所有未被标记的对象
标记完成后,引擎会扫描整个堆内存,把所有仍为“白色”的对象判定为不可达——也就是程序再也访问不到它们了。这些对象占用的内存会被立即释放,归还给可用内存池。
例如:
function createData() {
const temp = { x: 1 }; // 局部对象,仅在函数内可达
const keep = { y: 2 };
window.stored = keep; // 被全局引用,标记阶段会被保留
return keep;
}
createData(); // temp 在函数退出后失去所有引用,下次 GC 标记时不会被触及 → 清除
现代引擎的优化:不只是简单“全量扫描”
真实环境中的标记清除早已不是一次性大扫除,而是结合多种策略提升效率:
- 分代回收:把对象按存活时间分到“新生代”和“老生代”。新生代对象生命周期短,用 Scavenge 算法快速复制回收;老生代对象稳定,才启用标记-清除或标记-整理
- 增量标记:把标记过程拆成多个小任务,在 JS 主线程空闲时穿插执行,避免长时间停顿(STW)影响页面响应
- 并发标记:部分引擎支持在辅助线程中进行标记,主线程可继续运行脚本
开发者能做的配合:让对象更快变成“不可达”
虽然 GC 是自动的,但你可以减少误留引用导致的内存滞留:
- 及时解除全局变量、事件监听器、定时器对大对象的引用
- 不再需要的大数组或缓存,可设为 null 或清空内容(arr.length = 0)
- 避免无意创建循环引用(如父子对象互相持有),尤其在手动管理 DOM 结构时











