全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-16 8 分钟 ✍️ juanwangdev

JavaScript 垃圾回收机制

JavaScript 采用自动垃圾回收(GC),开发者无需手动管理内存,但理解其原理对性能优化至关重要。

垃圾回收基本概念

什么是垃圾

不再被引用的对象就是垃圾,需要被回收。

JavaScript
let obj = { name: 'test' };
obj = null; // { name: 'test' } 不再被引用,成为垃圾

可达性分析

从根对象(全局变量、当前栈变量)出发,无法到达的对象即为垃圾。

JavaScript
// 根对象
// - 全局变量
// - 当前调用栈中的变量

function test() {
  let a = { name: 'a' }; // a 可达
  let b = { name: 'b' }; // b 可达
  a.ref = b;            // b 被 a 引用
  b = null;             // b 仍通过 a.ref 可达
  a = null;             // a 和 b 都不可达,可被回收
}

回收算法

标记-清除(Mark-Sweep)

JavaScript
┌─────────────────────────────────────┐
│           标记-清除流程              │
├─────────────────────────────────────┤
│  1. 标记阶段:从根遍历,标记可达对象   │
│  2. 清除阶段:回收未标记对象          │
└─────────────────────────────────────┘
JavaScript
// 标记过程
const root = { a: 1 };
root.b = { c: 2 };

// 从 root 出发:
// root 可达 ✓
// root.a 可达 ✓
// root.b 可达 ✓
// root.b.c 可达 ✓

// 其他对象不可达,将被清除

标记-整理(Mark-Compact)

JavaScript
┌─────────────────────────────────────┐
│           标记-整理流程              │
├─────────────────────────────────────┤
│  1. 标记阶段:标记活动对象            │
│  2. 整理阶段:移动对象,消除碎片       │
└─────────────────────────────────────┘

引用计数(Reference Counting)

JavaScript
// 记录每个对象的引用次数
let obj = { name: 'test' }; // 引用计数:1

let ref = obj; // 引用计数:2

obj = null;    // 引用计数:1
ref = null;    // 引用计数:0 → 可回收

// ⚠️ 循环引用问题
function createCycle() {
  const a = {};
  const b = {};
  a.ref = b;
  b.ref = a; // 循环引用,引用计数无法回收
}

现代浏览器使用标记-清除,不再使用引用计数。

V8 分代回收

分代假说

  • 新生代:大部分对象生命周期短
  • 老生代:少数对象生命周期长

内存布局

JavaScript
┌─────────────────────────────────────────────┐
│                  V8 堆内存                    │
├──────────────────┬──────────────────────────┤
│     新生代        │         老生代            │
│   (New Space)    │      (Old Space)         │
├──────────────────┼──────────────────────────┤
│  Semi-space      │  Pointer Space           │
│  From  │  To     │  Data Space              │
│  1-8MB           │  较大                     │
├──────────────────┴──────────────────────────┤
│             大对象空间 (Large Object Space)   │
└─────────────────────────────────────────────┘

新生代回收(Scavenge)

JavaScript
┌─────────────────────────────────────┐
│           Scavenge 算法              │
├─────────────────────────────────────┤
│  From-Space → To-Space 复制活动对象   │
│  交换 From 和 To                      │
│  清空原 From(全是垃圾)               │
└─────────────────────────────────────┘
JavaScript
// 新生代分配
function createObjects() {
  const a = { x: 1 }; // 新生代
  const b = { y: 2 }; // 新生代
  return a; // a 晋升老生代,b 被回收
}

对象晋升

JavaScript
// 晋升条件
// 1. 经历过一次 Scavenge 回收
// 2. To-Space 使用超过 25%

function longLivingObject() {
  const obj = {}; // 新生代
  // 经历多次 GC 后晋升到老生代
  return obj;
}

老生代回收(Mark-Sweep-Compact)

JavaScript
// 标记-清除-整理
// 1. 标记所有活动对象
// 2. 清除未标记对象
// 3. 整理碎片(可选)

增量标记与并发回收

全停顿问题

JavaScript
// 传统 GC 会暂停应用
// ┌────────────────────────────────┐
// │ 应用执行 │  GC 暂停 │ 应用执行 │
// │          │  100ms   │          │
// └────────────────────────────────┘

增量标记

JavaScript
// 将 GC 分成多个小任务
// ┌──────────────────────────────────────────┐
// │ 应用 │ GC │ 应用 │ GC │ 应用 │ GC │ 应用 │
// │      │ 1  │      │ 2  │      │ 3  │      │
// └──────────────────────────────────────────┘

// 减少单次停顿时间

并发回收

JavaScript
// GC 在后台线程执行,不阻塞主线程
// 主线程              │ 辅助线程
// ────────────────────┼────────────────
// 应用执行            │
// 应用执行            │ GC 标记
// 应用执行            │ GC 标记
// 应用执行            │ GC 清除

内存泄漏检测

Chrome DevTools

  1. 打开 Memory 面板
  2. 选择 Heap Snapshot
  3. 对比快照发现增长对象

Performance Monitor

JavaScript
// 监控内存使用
if (process.memoryUsage) {
  setInterval(() => {
    const used = process.memoryUsage();
    console.log({
      rss: `${(used.rss / 1024 / 1024).toFixed(2)} MB`,
      heapTotal: `${(used.heapTotal / 1024 / 1024).toFixed(2)} MB`,
      heapUsed: `${(used.heapUsed / 1024 / 1024).toFixed(2)} MB`,
      external: `${(used.external / 1024 / 1024).toFixed(2)} MB`
    });
  }, 1000);
}

常见泄漏模式

text
// 1. 全局变量
function leak() {
  leakedGlobal = 'I am leaked'; // 挂载到 window
}

// 2. 闭包
function createClosure() {
  const bigData = new Array(1000000);
  return () => console.log(bigData.length); // 持续引用
}

// 3. DOM 引用
const elements = [];
document.getElementById('btn').addEventListener('click', () => {
  elements.push(document.createElement('div')); // 持续积累
});

// 4. 定时器
setInterval(() => {
  // 持续运行,引用外部变量
}, 1000);

GC 优化建议

减少垃圾产生

text
// ❌ 频繁创建对象
function animate() {
  const point = { x: 0, y: 0 }; // 每帧创建
  requestAnimationFrame(animate);
}

// ✅ 复用对象
const point = { x: 0, y: 0 };
function animate() {
  point.x = 0;
  point.y = 0;
  requestAnimationFrame(animate);
}

使用对象池

text
class ObjectPool {
  constructor(factory, reset) {
    this.pool = [];
    this.factory = factory;
    this.reset = reset;
  }

  acquire() {
    return this.pool.pop() || this.factory();
  }

  release(obj) {
    this.reset(obj);
    this.pool.push(obj);
  }
}

避免隐藏类转换

text
// ❌ 不同属性顺序
const a = { x: 1, y: 2 };
const b = { y: 2, x: 1 }; // 不同 Hidden Class

// ✅ 相同属性顺序
const a = { x: 1, y: 2 };
const b = { x: 1, y: 2 }; // 相同 Hidden Class

要点总结

概念说明
可达性从根出发能否到达对象
标记-清除标记活动对象,清除垃圾
分代回收新生代用 Scavenge,老生代用 Mark-Sweep
增量标记分批标记,减少停顿
并发回收后台 GC,不阻塞主线程
  • 现代浏览器使用标记-清除算法
  • 新生代小,回收频繁;老生代大,回收稀少
  • 避免全局变量、闭包、定时器导致的泄漏
  • 使用 DevTools 监控内存
  • 对象池可减少 GC 压力

📝 发现内容有误?点击此处直接编辑

← 上一篇 JavaScript 内存管理与优化
下一篇 → JavaScript 引擎架构
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库