V8引擎不采用引用计数作为主GC算法,因其无法解决循环引用导致的内存泄漏,且频繁更新计数器带来高开销;实际使用基于GC Roots的可达性分析(如Mark-Sweep)配合分代回收。

V8 引擎并不使用引用计数作为主垃圾回收算法,它只在极少数内部辅助场景(如某些底层对象生命周期管理)中可能用到类似计数的机制,但不对外暴露、不主导内存回收逻辑。真正驱动 V8 GC 的是基于可达性分析的标记类算法(如 Mark-Sweep、Mark-Compact)和分代回收策略。
引用计数在 JS 中是怎么工作的?
引用计数是一种独立的 GC 思路:每个对象维护一个计数器,记录有多少变量或属性正指向它。
- 声明
let a = {x: 1}→ 对象引用计数变为 1 - 再写
let b = a→ 计数变为 2 -
a = null或函数退出导致局部变量销毁 → 计数减 1 - 当计数降为 0 时,该对象立即被判定为垃圾并释放
为什么 V8 不用它做主要回收方式?
核心缺陷有两个,且都致命:
-
无法处理循环引用:两个对象互相持有对方引用(如
a.ref = b; b.ref = a),即使脱离全局作用域,它们的引用计数始终 ≥1,永远无法归零,造成内存泄漏 - 性能开销高:每次赋值、参数传递、作用域退出都要更新计数,频繁读写计数器拖慢执行速度,尤其在大量对象操作时明显
V8 实际采用什么替代方案?
它用从根出发的可达性分析来判断对象是否存活:
立即学习“Java免费学习笔记(深入)”;
- 根对象包括:全局变量、当前调用栈中的局部变量、正在执行的闭包环境等
- GC 从这些根开始,顺着引用链“爬”一遍,所有能到达的对象标记为“活跃”
- 未被标记的对象,哪怕存在内部循环引用,也被视为不可达,直接回收
这种机制天然规避了循环引用陷阱,也是现代 JS 引擎统一选择的原因。
那引用计数还有意义吗?
有,但它只起辅助作用:
- 某些底层资源(如 DOM 节点、事件监听器)内部可能用轻量级引用计数辅助生命周期管理
- 开发者调试时可借助
console.memory和 DevTools 的 Heap Snapshot 观察对象引用关系,间接理解“谁还在引用它” - 写代码时仍需注意避免人为制造长生命周期引用(比如缓存未清理、事件监听器未移除),否则即使 V8 能回收,也可能延迟或失效
真正影响内存的关键,从来不是“计数是否归零”,而是“是否还能从根访问到”。


















