内存与 GC
memory lifecycle、GC、泄漏排查。
30 分钟
内存与 GC
javascript-core 未涉及运行时内存;本课讲解 内存生命周期、垃圾回收(GC) 与 泄漏排查。长时间运行的 nodejs-essentials 服务与 react-essentials SPA 若泄漏,会表现为卡顿或 OOM。
学习目标
- 描述 JS 堆内存中对象如何分配与回收
- 识别常见泄漏模式(全局引用、闭包、定时器、DOM detached)
- 使用 Chrome DevTools Memory 面板初步定位
内存生命周期
// 1. 分配:声明变量、创建对象
let user = { name: 'Lee', lessons: [] };
// 2. 使用:读写属性
user.lessons.push('jsa-memory');
// 3. 释放:失去所有可达引用后,GC 可回收
user = null;
引擎区分栈(原始值、引用地址)与堆(对象、闭包环境)。
垃圾回收概览
现代引擎(V8)主要用 分代回收 + 标记清除:
新生代(Minor GC) → 存活对象晋升到老生代
老生代(Major GC) → 标记存活 → 清除/整理碎片
短生命周期对象(临时变量)在新生代快速回收;长期存活对象进入老生代,Full GC 成本更高。
常见泄漏模式
// 1. 意外全局变量(非 strict)
// leaked = { huge: new Array(1e6) };
// 2. 闭包持有大对象
function createHandler() {
const bigData = loadHugeCache();
return () => console.log(bigData.length); // bigData 无法释放
}
// 3. forgotten 定时器 / 监听器
const id = setInterval(() => {}, 1000);
// 组件卸载时未 clearInterval(id)
// 4. WeakMap 误用为强引用 Map 缓存 DOM
const cache = new Map();
document.querySelectorAll('.item').forEach((el) => {
cache.set(el, compute(el)); // el 移除 DOM 后 Map 仍强引用
});
修复:组件 cleanup 移除监听、用 WeakMap 关联 DOM、避免无界缓存。
弱引用与 FinalizationRegistry
const wm = new WeakMap();
let obj = {};
wm.set(obj, 'meta');
obj = null; // 条目可被 GC
// FinalizationRegistry(高级,少用于业务)
const registry = new FinalizationRegistry((held) => {
console.log('GC 了', held);
});
排查步骤
- Chrome DevTools → Memory → Heap snapshot(操作前/后各拍一张对比)
- 关注 Detached DOM tree、Array 异常增长
- Node:
node --expose-gc+process.memoryUsage()
function logMem(label) {
const { heapUsed, heapTotal } = process.memoryUsage();
console.log(label, Math.round(heapUsed / 1024 / 1024), 'MB');
}