3. 堆布局与垃圾回收
V8 的 GC 重点不是背源码类名,而是讲清楚:为什么分代、年轻代怎么收、老年代怎么收、如何降低停顿。
分代假说
V8 堆主要分为 新生代 和 老生代。
分代的依据是:大多数对象生命周期很短,刚创建不久就会死亡。把新对象集中放在新生代,可以让频繁 GC 的成本更低。
新生代
新生代常见思路是 复制回收:
- 对象先分配在新生代。
- GC 时只复制存活对象。
- 多次存活后晋升到老生代。
复制回收的优点是分配快、整理自然完成;缺点是需要预留空间,内存利用率有代价。
现代 V8 也有年轻代标记清除相关路径,可以根据配置和场景做取舍。中一般说“新生代偏复制,老生代偏标记整理”即可。
老生代
老生代对象生命周期更长,如果频繁复制成本太高,所以主要使用 Mark-Compact:
- 从根对象出发标记可达对象。
- 清理不可达对象。
- 必要时压缩整理,减少内存碎片。
降低停顿
GC 最大问题是 STW,也就是暂停 JS 执行。V8 通过以下方式降低卡顿:
- 增量标记:把一次大标记拆成多步。
- 并发标记 / 清理:部分工作放到后台线程。
- 任务调度:在合适时机触发 minor GC,避免分配路径突然卡住。
总结
V8 GC 的核心是分代回收:新生代对象多、死得快,适合低成本回收;老生代对象活得久,适合标记和整理。为了减少卡顿,V8 会把 GC 工作增量化、并发化,并通过写屏障维护正确性。