一、V8垃圾回收的核心基础:世代假说
V8的垃圾回收机制基于世代假说(Generational Hypothesis),该假说包含两个核心观点:
- 新生对象容易早死:大部分对象在创建后很快就不再使用(如函数内部的临时变量);
- 存活久的对象会更久存活:少数对象会长期存在(如全局变量、应用核心实例)。
基于此,V8将内存分为新生代(Young Generation)和老生代(Old Generation),分别采用不同的回收策略。
二、新生代垃圾回收:Scavenge算法
新生代用于存放新创建的对象或仅经历过一次回收的对象,内存空间较小:
- 64位系统:新生代总内存32MB,分为From(16MB)和To(16MB)两个等大空间;
- 32位系统:新生代总内存16MB,From和To各8MB。
Scavenge算法执行流程
- 对象分配:新对象优先分配到From空间;
- 回收触发:当From空间占满时,触发Scavenge回收;
- 存活对象筛选:
- 遍历From空间,标记存活对象;
- 若对象未存活:直接释放内存;
- 若对象存活:判断是否满足晋升老生代条件:
- 条件1:对象已经历过一次Scavenge回收;
- 条件2:To空间使用占比超过25%(避免To空间不足影响后续分配);
- 满足任一条件则晋升到老生代,否则复制到To空间;
- 空间交换:From和To空间角色互换(原To变为新From,原From清空)。
特点
- 优点:回收速度快(只处理小内存空间);
- 缺点:内存利用率低(始终有一半空间闲置);
- 停顿:回收时会暂停应用逻辑(STW),但因内存小,停顿时间极短。
三、老生代垃圾回收:标记-清除/压缩+增量标记
老生代用于存放经历过多次回收仍存活的对象,内存空间更大:
- 64位系统:老生代内存约1400MB;
- 32位系统:老生代内存约700MB。
1. 标记-清除(Mark-Sweep)
核心流程:
- 标记阶段:从根对象(如全局对象、调用栈)出发,遍历所有可达对象并标记;
- 清除阶段:遍历老生代内存,释放未标记的对象内存。
缺点:
- 产生内存碎片:多次回收后,空闲内存分散成小块,无法分配大对象。
2. 标记-压缩(Mark-Compact)
为解决内存碎片问题,在标记-清除后增加压缩阶段:
- 将存活对象向内存一端移动,紧凑排列;
- 更新对象引用地址,释放另一端的连续空闲内存。
3. 增量标记(Incremental Marking)
背景:
老生代内存大,全量标记会导致长时间STW(应用停顿),影响用户体验。
核心改进:
将全量标记拆分为多个小步骤,每执行一步就暂停回收,让应用逻辑运行一会,交替进行直到标记完成。
4. 黑白灰标记理论(三色标记法)
三色标记是增量标记的核心实现机制,通过颜色标记对象状态:
- 黑色:根对象或已遍历完所有子节点的存活对象(确定存活);
- 白色:未被标记的对象(可能是垃圾);
- 灰色:已标记但未遍历完子节点的对象(待处理)。
三色标记流程:
- 初始阶段:
- 根对象标记为灰色(待遍历);
- 其他对象为白色(未标记);
- 标记阶段:
- 取出灰色对象,标记为黑色;
- 遍历其所有子节点,未标记的子节点标记为灰色;
- 重复直到灰色队列清空;
- 清除阶段:白色对象判定为垃圾,释放内存。
写屏障(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