章节摘要:JavaScript 没有手动释放内存的语法,但内存照样会漏——因为回收器的判据是「可达性」,而代码里到处是延长可达链的路:未清理的定时器、遗落的 DOM 引用、无界增长的缓存、被闭包扣住的大对象。本章先讲 V8 的分代回收模型(新生代复制式、老生代标记清除加标记压缩、增量三色标记),再逐一复盘四类典型泄漏现场与排查工具,最后把前五章的引擎知识收拢成一份「与引擎合作」的性能清单。
阅读完本章,你应当能够:
内存管理的语法被拿走了,内存管理的责任没有——你不再手写 free,但你写下的每一条引用都在替回收器划定「什么不能回收」。
弱分代假说(多数对象朝生夕死)催生了分代设计:小而高频的新生代用复制式回收,大而稳定的老生代用标记清除加标记压缩。本节拆解两代算法、晋升条件、增量标记如何避免全停顿,以及一次小 GC 与大 GC 在时间线上的差别。
回收器只会「不可达才回收」,泄漏全部来自「可达但不再需要」。四种现场逐一复盘:忘清的定时器与监听器、detached 节点、无界缓存与数组、闭包扣留大对象——每种给出最小复现、排查入口与修复代码。
把前五章的引擎知识变现:形状稳定(第 3 章)、减少分配与避免强制同步布局(第 5 章)、长任务拆解与 Worker(第 4 章)、优化与反优化的触因(第 1 章)。给出可直接照做的清单与反模式对照。
6.1 是回收器的规则书,6.2 是规则书在错误代码上的投影,6.3 是规则书在正确代码上的应用。
可达性判据(根 → 引用链 → 堆) | +-- 6.1 回收何时发生 怎么发生 分代节奏 | +-- 6.2 引用链被意外拉长 → 泄漏四型 | +-- 6.3 主动缩短链 稳定形状 减少分配 → 性能清单
第 2 章闭包的环境引用、第 5 章 DOM 节点的 C++ 实体,都是本章引用链上的关键节点;第 1 章的优化与反优化是 6.3 的理论底座。
问:内存曲线上涨就是泄漏吗?
不一定,先看形状。正常缓存的锯齿基线平稳(涨跌对称),泄漏是基线单调上爬(只涨不跌)。浏览器自身也有缓存与预取行为,判断前先做「重复操作放大实验」:同一操作循环二十次,看增量是否线性累加——是,才值得进堆快照对比流程。
问:有没有「一键优化」让代码变快?
没有,这也是本章的立场。性能工作的顺序是测量、定位、按主轴修复、复测,跳过测量的任何改动都是猜。市面上「十倍优化技巧」类内容的问题不在技巧本身,在于它们默认你的瓶颈和作者的一样——而你的火焰图说了算。
问:这些 GC 知识对 Node 服务端也适用吗?
适用,V8 同款分代模型,且服务端更敏感:内存上涨影响容器指标与重启策略,长任务影响吞吐。差异点在观测工具——Node 用 trace-gc 标志与堆快照文件,浏览器用面板,思路一致:先看分配速率,再查可达链。
问:三节的学习顺序可以调换吗?
6.1 的分代与可达性是理解其余两节的前提,建议按序读;6.2 与 6.3 可以按需跳读——正在救火的先读 6.2(泄漏排查是急救术),做架构评审的先读 6.3(清单是预防医学)。三节合起来是一个闭环:懂回收规则、会查泄漏、能写出不制造问题的代码,缺任何一角,内存问题都会以更隐蔽的方式回来找你。
自测三题:其一,说出对象从出生到回收可能经过的两次搬家路线(新生代半区复制、晋升老生代),以及各自的触发条件;其二,四种泄漏形态各自「断链」的修复动作分别是什么;其三,拿到一段卡顿代码,按四主轴(形状、分配、主线程、优化器)逐项体检的顺序与手段。能对答如流,就可以带着完整的内存与性能模型进入第 7 章,收束全书的学习。