垃圾回收机制核心知识点

一、垃圾回收原理篇

1. 核心概念

垃圾回收(GC) 是自动管理内存的机制,用于识别并回收不再被程序使用的内存空间,避免内存泄漏。

关键问题:

  • 哪些内存需要回收?
    不再被任何活动对象引用的内存(即"不可达"内存)
  • 何时回收?
    由引擎在空闲时间或内存不足时自动触发

2. 常见垃圾回收算法

(1) 引用计数(Reference Counting)

  • 原理:记录每个对象被引用的次数,当引用数为0时回收

  • 优点:即时回收,无暂停

  • 缺点:无法处理循环引用(经典内存泄漏)

    ini 复制代码
    // 循环引用示例
    let objA = { ref: null };
    let objB = { ref: null };
    objA.ref = objB;
    objB.ref = objA; // 两者引用数永远 >=1,无法回收

(2) 标记-清除(Mark-Sweep) 🌟(主流浏览器使用)

  • 原理

    1. 标记阶段:从根对象(全局变量、活动函数等)出发,标记所有可达对象
    2. 清除阶段:遍历堆内存,回收未被标记的对象
  • 优点:解决循环引用问题

  • 缺点:内存碎片化

(3) 标记-整理(Mark-Compact)

  • 改进点:在标记后,将存活对象整理到连续内存空间,解决碎片化问题
  • 应用场景:V8 老生代内存回收

(4) 分代回收(Generational Collection)

  • 核心思想 :将内存分为 新生代(Young Generation)老生代(Old Generation) ,分别采用不同策略

    • 新生代 :存活时间短的对象,使用 Scavenge 算法(复制存活对象到新空间)
    • 老生代 :存活时间长的对象,使用 标记-清除/整理

二、V8 引擎篇

1. V8 内存结构

css 复制代码
V8 堆内存
├── 新生代 (1-8MB)
│   ├── From 空间
│   └── To 空间
└── 老生代

2. 新生代回收(Scavenge 算法)

  • 过程

    1. 将新生代分为 FromTo 两个等大空间
    2. 新对象分配到 From 空间
    3. From 满时,标记存活对象并复制到 To 空间
    4. 清空 From 空间,交换 From 和 To 角色
  • 晋升条件

    对象经历一次 Scavenge 回收仍然存活,或 To 空间使用超过 25% → 晋升到老生代

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

  • 标记阶段:三色标记法(白→灰→黑)遍历可达对象
  • 清除/整理阶段:回收未标记内存,整理碎片

4. V8 优化策略

  • 增量标记(Incremental Marking) :将标记过程拆分为小任务,避免长时间阻塞主线程
  • 空闲时间收集(Idle-Time GC) :利用渲染间隔执行 GC 任务

三、内存泄漏篇

1. 常见内存泄漏场景

场景1:未清理的定时器/事件监听

javascript 复制代码
// 泄漏示例
function LeakComponent() {
  useEffect(() => {
    setInterval(() => {
      console.log('Leaking...');
    }, 1000);
  }, []);
  return <div>Leak Demo</div>;
}

场景2:闭包保留大对象

javascript 复制代码
function createClosure() {
  const hugeArray = new Array(1000000).fill('*');
  return () => console.log('Closure'); // hugeArray 被闭包引用无法回收
}

场景3:游离的 DOM 引用

javascript 复制代码
let elements = {
  button: document.getElementById('myButton'), // 即使DOM被移除,引用仍在
  image: document.getElementById('myImage')
};

// 移除DOM后,elements 仍保留引用 → 无法回收
document.body.removeChild(document.getElementById('myButton'));

2. 内存泄漏排查工具

  • Chrome DevTools

    • Memory 面板:Heap Snapshots 对比内存变化
    • Performance 面板:记录内存分配时间线
    • Performance Monitor:实时监控 JS Heap 大小

四、面试题篇

高频问题1:闭包一定会导致内存泄漏吗?

不一定。闭包导致内存泄漏的 必要条件 是闭包中引用了不再需要的大对象,且未及时解除引用。现代浏览器通过优化(如 V8 的逃逸分析)可自动回收未使用的闭包变量。


高频问题2:WeakMap 和 WeakSet 如何帮助内存管理?

  • WeakMap/WeakSet 的键是 弱引用,不会阻止垃圾回收

  • 应用场景

    scss 复制代码
    // 使用 WeakMap 缓存大对象
    const cache = new WeakMap();
    function getHeavyData(obj) {
      if (!cache.has(obj)) {
        const data = heavyCalculation(obj);
        cache.set(obj, data);
      }
      return cache.get(obj);
    }
    // 当 obj 被回收时,对应的缓存数据自动释放

高频问题3:如何手动触发垃圾回收?

  • 浏览器环境(仅 Chrome 支持):

    javascript 复制代码
    if (window.gc) {
      window.gc(); // 需启动 Chrome 时加参数 --js-flags="--expose-gc"
    }
  • Node.js 环境

    csharp 复制代码
    global.gc(); // 启动时需加参数 --expose-gc

高频问题4:如何避免 DOM 内存泄漏?

  1. 移除 DOM 后手动解除引用:

    ini 复制代码
    const btn = document.getElementById('myBtn');
    btn.remove(); // 移除DOM
    btn = null;   // 解除引用
  2. 使用 WeakRef(ES2021):

    javascript 复制代码
    const domRef = new WeakRef(document.getElementById('myBtn'));
    // 通过 domRef.deref() 访问,DOM移除后自动解除

高频问题5:描述 V8 的增量标记算法原理

  • 目标 :减少 GC 造成的 主线程阻塞时间

  • 原理

    1. 将标记过程拆分为多个小任务
    2. 在每个 V8 的 增量标记步骤 中执行部分标记
    3. 穿插在 JavaScript 任务之间执行
  • 效果:大幅减少单次 GC 停顿时间


五、代码实战:内存泄漏检测

使用 Chrome DevTools 定位泄漏

xml 复制代码
<button id="leakBtn">创建泄漏</button>
<script>
  let leakedObjects = [];

  document.getElementById('leakBtn').addEventListener('click', () => {
    // 每次点击创建 1MB 的数组并保留引用
    leakedObjects.push(new Array(1024 * 1024).fill('*'));
  });
</script>

运行 HTML

检测步骤:

  1. 打开 Chrome DevTools → Memory 面板
  2. 记录初始 Heap Snapshot
  3. 多次点击按钮创建泄漏
  4. 记录第二次 Heap Snapshot
  5. 对比两次快照,筛选 Array 对象查看增长情况

六、总结:前端内存管理最佳实践

  1. 避免全局变量:使用模块化或 IIFE 封装
  2. 及时清理资源:定时器、事件监听、第三方库实例
  3. 慎用闭包:确保不保留无用的大对象
  4. 使用弱引用WeakMap/WeakSet/WeakRef
  5. 监控内存:定期使用 DevTools 检测

掌握这些知识后,你不仅能应对面试,更能写出高性能的前端应用!

相关推荐
烛阴16 分钟前
JavaScript 函数对象与 NFE:你必须知道的秘密武器!
前端·javascript
eli96020 分钟前
node-ddk,electron 开发组件
前端·javascript·electron·node.js·js
老K(郭云开)40 分钟前
最新版Chrome浏览器加载ActiveX控件技术--allWebPlugin中间件一键部署浏览器扩展
前端·javascript·chrome·中间件·edge
老K(郭云开)41 分钟前
allWebPlugin中间件自动适应Web系统多层iframe嵌套
前端·javascript·chrome·中间件
还是鼠鼠1 小时前
Node.js 的模块作用域和 module 对象详细介绍
前端·javascript·vscode·node.js·web
拉不动的猪1 小时前
刷刷题36(uniapp高级实际项目问题-1)
前端·javascript·面试
勘察加熊人2 小时前
angular打地鼠
前端·javascript·angular.js
柒@宝儿姐2 小时前
如何判断一个项目用的是哪个管理器
前端·javascript·vue.js·vue3
爱看书的小沐3 小时前
【小沐学Web3D】three.js 加载三维模型(vue3)
javascript·vue·vue3·webgl·three.js·opengl·web3d
敲敲敲敲暴你脑袋3 小时前
高德地图自定义canvas实现聚类分析
javascript·数据可视化·canvas