Skip to content

九、浏览器垃圾回收机制

1. V8的垃圾回收机制是

1. 什么是垃圾回收(Garbage Collection, GC)?

V8 是 Chrome 和 Node.js 运行 JavaScript 代码的引擎,它采用 自动垃圾回收 机制,负责回收不再被使用的内存,防止内存泄漏,提高 JavaScript 程序的运行效率。


2. V8 的内存管理

在 V8 引擎中,JavaScript 的对象和变量存储在堆(Heap)和栈(Stack)中

  • 栈(Stack):用于存储基本数据类型NumberStringBoolean 等)和函数调用上下文,内存占用小,访问速度快。
  • 堆(Heap):用于存储复杂数据类型(对象、数组、函数),垃圾回收主要针对堆内存。

3. V8 的垃圾回收策略

V8 采用分代回收机制(Generational Garbage Collection),将堆内存分为新生代(Young Generation)老生代(Old Generation),分别采用不同的回收算法。

3.1 新生代(Young Generation)

  • 存放生命周期短的对象(如局部变量、临时对象)。
  • 采用 Scavenge 算法 进行垃圾回收。
Scavenge 算法(新生代回收)
  1. 新生代的堆分成两个区域(Semi-Space):

    • From 空间(活跃区):存放正在使用的对象。
    • To 空间(空闲区):用于 GC 复制存活对象。
  2. GC 过程

    • 执行 Scavenge 回收时,存活对象被复制到 To 空间,From 空间清空
    • 交换 From 和 To 空间的角色。
    • 复制次数超过一定阈值的对象,会被移动到老生代(晋升为老生代对象)

💡 特点

  • 效率高,因为每次只处理存活的对象,回收速度快。
  • 适用于短生命周期对象,避免长时间占用内存。

3.2 老生代(Old Generation)

  • 存放生命周期较长的对象(如全局变量、闭包)。
  • 采用 标记-清除(Mark-Sweep)标记-整理(Mark-Compact) 算法回收。
标记-清除(Mark-Sweep)
  1. 标记(Mark):遍历堆内存,标记所有可达对象
  2. 清除(Sweep)回收未标记的对象,释放内存。

🚫 问题

  • 会产生内存碎片(释放的空间不连续),可能影响性能。
标记-整理(Mark-Compact)
  • 标记可达对象后,将存活对象移动到堆的一侧然后清除剩余空间
  • 减少内存碎片,提高分配效率

💡 特点

  • 适用于生命周期长的对象,如全局对象、闭包等。
  • 比 Scavenge 慢,但适合大对象管理

4. V8 的增量回收 & 并发回收

4.1 增量标记(Incremental Marking)

  • 将标记阶段拆成多个小任务,减少主线程阻塞时间,提高页面流畅度。

4.2 并发回收(Concurrent GC)

  • GC 操作和 JavaScript 代码同时执行,提高性能。

5. 触发垃圾回收的情况

  • 新生代空间满时(触发 Scavenge 回收)。
  • 老生代空间满时(触发 Mark-Sweep 或 Mark-Compact)。
  • 显式调用 global.gc()(Node.js 需要 --expose-gc)。
  • 浏览器空闲时(现代浏览器会利用空闲时间执行 GC)。

6. 内存泄漏 & 解决方案

常见的 JavaScript 内存泄漏

内存泄漏类型说明解决方案
全局变量未使用 var/let/const 声明的变量use strict 及时释放变量
闭包闭包引用外部变量,导致对象无法回收适当手动清理引用
未清理的定时器setInterval() 未清除,导致变量无法释放clearInterval()
DOM 引用未清除事件监听未移除removeEventListener()

7. 总结

V8 GC 机制策略
新生代Scavenge 算法(复制存活对象)
老生代Mark-Sweep / Mark-Compact(标记清除 & 整理)
优化增量标记、并发 GC
触发条件新生代/老生代空间满、手动 GC

🔥 V8 采用高效的分代回收机制,优化内存管理,提高 JavaScript 执行效率。

2. 哪些操作会造成内存泄漏?

JavaScript 运行在 V8 引擎上,使用 垃圾回收(GC) 机制来自动释放不再使用的对象。但如果某些对象仍然被意外引用,就会无法被回收,从而导致内存泄漏(Memory Leak)

1. 什么是内存泄漏?

内存泄漏 指的是程序运行过程中,已经不再需要的对象仍然占用内存,导致内存无法被回收,最终可能引发:

  • 性能下降(占用过多内存)
  • 页面卡顿、崩溃(内存溢出)

2. 导致 JavaScript 内存泄漏的 5 大原因

❌ 1. 全局变量(Global Variables)

问题

  • 如果变量没有使用 var / let / const 关键字声明,它会变成全局变量,一直存在于 window 对象上,无法回收。

示例

js
function memoryLeak() {
  leak = "This is a memory leak"; // 没有 `var / let / const`
}
memoryLeak();
console.log(window.leak); // "This is a memory leak"

解决方案使用 use strict 严格模式

js
"use strict";
function memoryLeak() {
  let leak = "This is safe"; // 避免成为全局变量
}
memoryLeak();

❌ 2. 未清除的定时器和回调

问题

  • setInterval()setTimeout() 会持续引用对象,如果没有手动清除,就会造成内存泄漏

示例

js
let element = document.getElementById("leakElement");

setInterval(() => {
  console.log(element.innerText); // 仍然引用 element
}, 1000);

// 即使 element 被删除,setInterval 仍然持有引用,导致泄漏
document.body.removeChild(element);

解决方案在合适的时机清除定时器

js
let element = document.getElementById("leakElement");
let interval = setInterval(() => {
  if (!element) {
    clearInterval(interval);
  }
}, 1000);

// 当元素被删除时,手动清理
document.body.removeChild(element);
element = null;

❌ 3. 闭包(Closures)导致的意外引用

问题

  • 闭包会持有对外部变量的引用,如果不正确释放,这些变量就无法被回收。

示例

js
function createLeak() {
  let largeArray = new Array(1000000).fill("I am a leak");

  return function() {
    console.log(largeArray[0]); // largeArray 一直被引用
  };
}

let leakFunction = createLeak();

解决方案在合适的时候手动释放引用

js
function createSafeFunction() {
  let largeArray = new Array(1000000).fill("I am safe");

  return function() {
    console.log(largeArray[0]);
    largeArray = null; // 手动释放
  };
}

let safeFunction = createSafeFunction();
safeFunction(); // 访问后释放

❌ 4. DOM 元素引用未清理

问题

  • 如果删除了 DOM 元素,但 JavaScript 仍然持有对它的引用,垃圾回收无法释放该元素。

示例

js
let div = document.getElementById("myDiv");

document.body.removeChild(div); // 删除元素
console.log(div); // div 仍然被 JavaScript 引用,无法回收

解决方案手动释放引用

js
let div = document.getElementById("myDiv");
document.body.removeChild(div);
div = null; // 释放引用

❌ 5. 事件监听未清除

问题

  • addEventListener() 绑定的事件会一直保留,即使元素被删除,也不会自动释放。

示例

js
let btn = document.getElementById("myButton");

btn.addEventListener("click", function () {
  console.log("按钮被点击");
});

// 删除元素
document.body.removeChild(btn);

但事件仍然存在,无法回收!

解决方案在删除元素前,手动移除事件

js
btn.removeEventListener("click", handleClick);
document.body.removeChild(btn);
btn = null;

3. 如何检测内存泄漏?

✅ 使用 Chrome 开发者工具

  1. 打开 DevTools(F12 → PerformanceMemory
  2. 运行代码并观察内存使用情况
  3. 使用 Heap Snapshot 检查对象
  4. 观察 Detached DOM elements(未回收的 DOM)

4. 如何避免 JavaScript 内存泄漏?

最佳实践

  1. 使用 let / const,避免意外的全局变量
  2. 定时器(setIntervalsetTimeout)在不需要时清除
  3. 闭包中不再使用的变量手动置 null
  4. 删除 DOM 时,清除事件监听
  5. 使用 WeakMap / WeakSet(自动回收弱引用)
  6. 使用 Chrome DevTools 检测内存泄漏

5. 总结

问题原因解决方案
全局变量未用 var / let / const开启 use strict
定时器未清除setInterval() 持续引用clearInterval()
闭包引用变量一直被函数引用null
DOM 未清除删除 DOM 但仍有 JS 引用element = null;
事件监听未清除addEventListener() 仍存在removeEventListener()

🔥 结论

  • JavaScript 采用垃圾回收(GC)机制,但错误引用会导致内存泄漏。
  • 合理使用 let / const,清除定时器、事件监听、DOM 引用,避免内存泄漏。
  • 用 DevTools 监测内存,及时优化!

评论区

欢迎留言、补充或勘误。

xiaoba.blog