ECMAScript内存管理

JavaScript 内存分配、可达性、垃圾回收与常见泄漏场景。

#type / concept #status / growing #resource / javascript #resource / ecmascript

[!info] related notes

ECMAScript内存管理

JavaScript 会自动分配和回收内存,但自动回收不代表代码天然不会泄漏。理解这篇的核心,是抓住“可达性”和“引用生命周期”。

先抓住两个概念

  • 垃圾回收(GC): 自动识别并释放不再使用的内存
  • 可达性(reachability): 从根对象出发无法再访问到的值,才有机会被回收

这里的根对象通常包括全局对象、当前调用栈上的局部变量、仍被宿主保存的回调引用等。

常见回收思路

标记-清除

  • 先从根对象出发标记所有可达对象
  • 再回收没有被标记的对象
  • 能处理循环引用,是现代引擎的主流思路

引用计数

  • 为对象记录被引用次数
  • 引用数降到 0 时回收
  • 直观但难处理循环引用,所以现在更多作为历史对照来理解

现代引擎常见优化

  • 分代回收: 把短命对象和长寿对象分开处理
  • 增量标记: 把长时间标记拆成多个小步骤,减少卡顿
  • 闲时回收: 在较空闲的时间片执行部分 GC 工作

这也是为什么“对象创建很多”不一定立刻出问题,但不必要的长生命周期引用通常更危险。

回收什么时候发生

GC 不是在你调用某个固定 API 时立刻执行,也不是和浏览器绘制同步触发。

  • 具体回收时机由 JS 引擎决定
  • 通常会避开当前同步执行的关键路径
  • 可能在任务切换、空闲时段、或引擎认为合适的时机运行
  • 浏览器页面是否 repaint,不等于 GC 是否发生

所以,真正要关注的不是“什么时候手动回收”,而是“有没有不必要地把对象一直保持可达”。

实践里最值得先做的事

  • 只保留还需要的数据引用
  • 大数组或大对象不再需要时,及时解除引用
  • 定时器、事件监听、订阅关系用完就清理
  • 循环里反复创建但可复用的函数或对象,尽量复用
  • 对缓存和闭包保存的数据保持克制

补充直觉:把变量设为 null 或让其离开作用域,意义不在“手动释放内存”,而在于切断引用,让 GC 可以判断它是否仍然可达。

常见泄漏场景

  • 意外的全局变量
  • 被遗忘的 setInterval、回调或订阅
  • 已脱离文档但仍被代码引用的 DOM
  • 长期持有外部状态的闭包或缓存

内存泄漏和内存溢出的区别

  • 内存泄漏: 本来该释放的内存仍被保留
  • 内存溢出: 当前需要的内存超过了可用上限

泄漏往往是溢出的诱因之一,但两者不是同一个概念。

延伸阅读

参考资料

创建于 2025/1/1 更新于 2026/5/27