V8 引擎的垃圾回收机制是怎样的?
V8 垃圾回收策略
V8 采用分代回收机制,将堆内存分为新生代和老生代。
新生代(Scavenge 算法)
- 存放存活时间短的对象(1~8MB)
- 分为 From 空间 和 To 空间
回收过程:
- 从 GC Root 遍历,标记 From 空间中存活的对象
- 将存活对象复制到 To 空间
- 清空 From 空间
- From 和 To 角色互换
特点:空间换时间,适合存活率低的场景
老生代(Mark-Sweep + Mark-Compact)
- 存放存活时间长、体积大的对象
- 经历过 2 次 Scavenge 回收仍存活的对象晋升到老生代
Mark-Sweep(标记清除):
- 标记阶段:从 GC Root 遍历,标记所有可达对象
- 清除阶段:清除未标记的对象
问题:产生内存碎片
Mark-Compact(标记整理):
- 标记存活对象
- 将存活对象向一端移动,紧凑排列
- 清除边界以外的内存
特点:解决碎片问题,但移动对象有开销
增量标记与并发回收
- 增量标记(Incremental):将标记任务拆分成小步骤,穿插在 JS 执行之间,避免长时间停顿
- 并发回收(Concurrent):GC 在后台线程执行,主线程不暂停
引用计数(已弃用)
V8 早期尝试过引用计数,但有循环引用问题:
let a = {}
let b = {}
a.ref = b
b.ref = a
// 引用计数永远不为 0,无法回收现代 V8 使用可达性分析,彻底解决此问题。
常见内存泄漏场景
- 意外的全局变量:
function foo() { bar = 1 }(未用 let/var) - 被遗忘的定时器:
setInterval未清除 - 闭包引用:不必要的大对象被闭包持有
- DOM 引用:元素被移除但 JS 变量仍引用
调试工具
- Chrome DevTools → Memory 面板 → Heap snapshot
performance.memory(非标准 API)--trace-gcV8 启动参数