垃圾回收
约 1698 字大约 6 分钟
2026-02-26
JavaScript 采用自动内存管理,开发者不需要像 C/C++ 那样手动 malloc/free,但这不代表万事大吉了。很多卡顿、泄漏、页面长时间运行后变慢,本质都和垃圾回收机制有关
| 策略 | 代表语言 | 核心特点 | 主要代价 |
|---|---|---|---|
| 手动回收 | C / C++ | 何时分配、何时释放由程序员控制 | 易出现内存泄漏、悬挂指针 |
| 自动回收 | JavaScript / Java | 由垃圾回收器判断对象是否可回收 | 回收时可能造成暂停(STW) |
栈内存中的垃圾回收
栈内存(调用栈)回收靠的是栈顶指针(常用 ESP 表示)回退。谁在栈顶,谁就是当前正在执行的上下文
function foo() {
var a = 1
var b = { name: '极客邦' }
function showName() {
var c = 2
var d = { name: '极客时间' }
}
showName()
}
foo()执行过程
showName调用前,V8 创建showName执行上下文并压栈ESP指向showName,说明当前主线程正在执行它showName执行结束后,ESP下移,重新指向foo上下文- 被 "移出栈顶" 的那段内存不再有效,后续新栈帧可以直接覆盖它
- 当 foo 函数执行完成后,foo 函数的执行上下文会从栈中被销毁

堆内存中的垃圾回收
和栈不同,堆中的对象不会因函数返回自动消失。函数退出后,只是 "变量绑定关系" 可能断开,堆对象是否回收要由 GC 通过 "可达性" 来判断。要回收堆空间中的垃圾数据,就需要使用 JS 中的垃圾回收器

代际假说
V8 的分代策略建立在代际假说之上,V8 会按对象生命周期把堆拆分处理(分代处理):
- 大多数对象 "朝生夕死",存活时间很短
- 少数对象一旦存活下来,通常会活得更久
| 代际 | 存放对象 | 空间特点 | 主要回收器 |
|---|---|---|---|
| 新生代(Young) | 新创建、短生命周期对象 | 空间小、回收频繁 | 副垃圾回收器 |
| 老生代(Old) | 存活久或体积较大的对象 | 空间大、回收谨慎 | 主垃圾回收器 |
垃圾回收流程
重要
不同生代的部分虽然会使用不同的垃圾回收器,但它们共用一套执行流程
- 区分活动对象(还在使用的对象)和非活动对象(可进行回收的对象)
- 回收非活动对象所占据的内存
- 回收非活动对象后导致内存空间不连续,存在内存碎片,需要整理内存空间
副垃圾回收器
大多数的小对象都会被分配到新生区,所以虽然区域不大,但垃圾回收比较频繁。副垃圾回收采用 Scavenge 算法来处理
该算法把新生代空间划分为两部分:对象区域、空闲区域
- 新加入的对象都会存放到对象区域,当对象区域快被写满时,执行一次垃圾清理操作
- 副 GC 只复制 "存活对象" 到空闲区,并按顺序紧凑排列
- 复制完成后,两块区域角色互换(翻转)
- 原对象区整体变为空闲区,等待下一轮分配

优势
- 回收速度快(只关心存活对象)
- 复制天然带整理,几乎不产生碎片
对象晋升策略
因为复制、清理,都需要时间。因此为了执行效率,一般新生区的空间会被设置得比较小。而小空间很容易被存活的对象装满整个区域,为解决这个问题,JS 引擎采取了对象晋升策略,也就是经历过两次垃圾回收依然还存活的对象直接进去老生区
主垃圾回收器
主垃圾回收器主要负责老生区中的垃圾回收,一些大的对象会直接被分配到老生区。因此老生代对象多、体积大、引用关系复杂,回收过程更谨慎。主垃圾回收采用 "标记 - 清除" 算法进行垃圾回收
标记(Mark)
标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据

标记过程 清除(Sweep):回收未标记对象占用的内存

标记清除 整理(Compact,可选)
当多次执行 "标记 - 清除" 算法后,会产生大量不连续的内存碎片。而碎片过多会导致大对象无法分配到足够的连续内存,于是就产生了另一种 ----- "标记 - 整理(Mark - Compact)" 算法,让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

标记整理过程
全停顿
由于 JS 是运行在主线程上的,一旦执行 GC,都需要阻塞后续脚本的执行,待垃圾回收完成脚本恢复执行,这种行为就叫全停顿(Stop-The-World)。如果老生代回收一次占用几十到几百毫秒,动画、交互、输入响应都会被阻塞

为降低老生代长停顿,V8 把完整标记任务拆成多个小步骤,与 JS 执行交替进行
增量标记
- 先做初始标记
- 将后续标记切成多个子任务
- 在 JS 执行的间隙穿插这些子任务
- 最终汇总并进入后续清理阶段

