Published on

V8垃圾回收机制全解析(分代回收+黑白灰标记)

一、V8垃圾回收的核心基础:世代假说

V8的垃圾回收机制基于世代假说(Generational Hypothesis),该假说包含两个核心观点:

  1. 新生对象容易早死:大部分对象在创建后很快就不再使用(如函数内部的临时变量);
  2. 存活久的对象会更久存活:少数对象会长期存在(如全局变量、应用核心实例)。

基于此,V8将内存分为新生代(Young Generation)和老生代(Old Generation),分别采用不同的回收策略。

二、新生代垃圾回收:Scavenge算法

新生代用于存放新创建的对象或仅经历过一次回收的对象,内存空间较小:

  • 64位系统:新生代总内存32MB,分为From(16MB)和To(16MB)两个等大空间;
  • 32位系统:新生代总内存16MB,From和To各8MB。

Scavenge算法执行流程

  1. 对象分配:新对象优先分配到From空间;
  2. 回收触发:当From空间占满时,触发Scavenge回收;
  3. 存活对象筛选
    • 遍历From空间,标记存活对象;
    • 若对象未存活:直接释放内存;
    • 若对象存活:判断是否满足晋升老生代条件:
      • 条件1:对象已经历过一次Scavenge回收;
      • 条件2:To空间使用占比超过25%(避免To空间不足影响后续分配);
      • 满足任一条件则晋升到老生代,否则复制到To空间;
  4. 空间交换:From和To空间角色互换(原To变为新From,原From清空)。

特点

  • 优点:回收速度快(只处理小内存空间);
  • 缺点:内存利用率低(始终有一半空间闲置);
  • 停顿:回收时会暂停应用逻辑(STW),但因内存小,停顿时间极短。

三、老生代垃圾回收:标记-清除/压缩+增量标记

老生代用于存放经历过多次回收仍存活的对象,内存空间更大:

  • 64位系统:老生代内存约1400MB;
  • 32位系统:老生代内存约700MB。

1. 标记-清除(Mark-Sweep)

核心流程:

  • 标记阶段:从根对象(如全局对象、调用栈)出发,遍历所有可达对象并标记;
  • 清除阶段:遍历老生代内存,释放未标记的对象内存。

缺点:

  • 产生内存碎片:多次回收后,空闲内存分散成小块,无法分配大对象。

2. 标记-压缩(Mark-Compact)

为解决内存碎片问题,在标记-清除后增加压缩阶段

  • 将存活对象向内存一端移动,紧凑排列;
  • 更新对象引用地址,释放另一端的连续空闲内存。

3. 增量标记(Incremental Marking)

背景:

老生代内存大,全量标记会导致长时间STW(应用停顿),影响用户体验。

核心改进:

将全量标记拆分为多个小步骤,每执行一步就暂停回收,让应用逻辑运行一会,交替进行直到标记完成。

4. 黑白灰标记理论(三色标记法)

三色标记是增量标记的核心实现机制,通过颜色标记对象状态:

  • 黑色:根对象或已遍历完所有子节点的存活对象(确定存活);
  • 白色:未被标记的对象(可能是垃圾);
  • 灰色:已标记但未遍历完子节点的对象(待处理)。

三色标记流程:

  1. 初始阶段
    • 根对象标记为灰色(待遍历);
    • 其他对象为白色(未标记);
  2. 标记阶段
    • 取出灰色对象,标记为黑色
    • 遍历其所有子节点,未标记的子节点标记为灰色
    • 重复直到灰色队列清空;
  3. 清除阶段:白色对象判定为垃圾,释放内存。

写屏障(Write Barrier):

增量标记过程中,若应用逻辑修改对象引用(如将黑色对象指向白色对象),会触发写屏障,将新引用的白色对象重新标记为灰色,避免误回收。

四、V8垃圾回收的优化机制

1. 并发回收(Concurrent Marking)

V8后续引入的优化,标记阶段与应用逻辑并行执行,进一步减少STW时间。

2. 惰性清理(Lazy Sweeping)

标记完成后,不立即全量清除垃圾,而是在分配内存时按需清理部分区域,分散清理开销。

3. 代际晋升阈值调整

动态调整新生代对象晋升到老生代的条件,平衡新生代和老生代的回收压力。

五、总结

内存区域回收算法核心特点
新生代Scavenge速度快、STW短、内存利用率低
老生代标记-清除/压缩处理大内存、解决碎片问题
增量标记三色标记法拆分回收步骤,减少STW时间

V8通过分代回收+多算法组合,在回收效率和应用响应性之间取得平衡,是前端和Node.js性能优化的重要基础。

flowchart TD
    A[对象创建] --> B{分配到新生代From空间}
    B --> C{From空间满?}
    C -- 否 --> A
    C -- 是 --> D[执行Scavenge算法]
    D --> E{对象存活?}
    E -- 否 --> F[释放内存]
    E -- 是 --> G{满足晋升条件?}
    G -- 是 --> H[晋升到老生代]
    G -- 否 --> I[复制到To空间]
    I --> J[From/To空间交换]
    J --> A
    H --> K{老生代内存不足?}
    K -- 是 --> L[执行标记-清除/压缩]
    L --> M[增量标记(三色标记)]
    M --> N[清除垃圾/压缩内存]
    N --> K