垃圾回收与内存泄露

114 阅读2分钟

垃圾回收

由来

在JavaScript中,每次你创建一个对象、函数等操作,都会占用内存;如果你不及时释放内存,内存很快就会用完,而不在被程序需要的对象就变成垃圾,导致程序性能下降甚至崩溃。

概念

垃圾回收是自动内存管理机制,用于检测和释放程序中不再使用的内存,避免开发者手动管理内存的复杂性。

非原始类型(对象、函数、数组等)不再被需要,它就会从内存中删除,并且后台使用不同的算法来尝试何时不再需要它。

而垃圾回收器就是将它从内存里删去的“人”,它会持续追踪内存使用情况,自动识别并释放不再被需要的资源。

工作原理

1.标记-清除算法(Mark-and-Sweep)

  • 首先,第一个垃圾回收器会创建一个路由列表,这些路由通常是全局变量,

  • 在JavaScript代码中引用了这些全局变量,window对象就是一个这样的全局变量,它充当根对象。

  • 根对象(全局变量、当前执行上下文中的变量等)出发,标记所有可以到达的对象,认为是非垃圾,随后清除未标记的对象,从而有效地返回内存。

2. 分代回收(Generational GC)

  • 新生代:存活时间短的对象(如局部变量),使用复制算法(Scavenge)快速回收。
  • 老生代:存活时间长的对象(如全局变量),使用标记-清除标记-压缩算法。

内存泄漏

未声明或意外的全局变量被意外保留引用,垃圾收集器无法清除它们,这就可能导致内存泄漏。

为了解决这个问题,可以在使用它们之后将这些变量设为无效的启用严格模式等等

一些场景示例

场景一 :意料之外的全局变量

function leak() {
    leakedVar = '全局变量'; // 未用 var/let/const 声明 -> 成为全局变量
}

场景二:被闭包引用

function closure() {
    const data = 3;
    return () => console.log(data); // data 被闭包引用,无法释放
}
const c = closure(); // 长期持有 data

场景三:忘记清除计时器或回调

const intervalId = setInterval(() => {}, 1000);
// 忘记 clearInterval(intervalId) → 回调函数及闭包变量无法释放

场景四:DOM元素未解绑事件

const button = document.getElementById('myButton');
button.addEventListener('click', handleClick);
// 若移除 button 前
// 未使用button.removeEventListener('click', onClick)解绑事件监听
// 那么handleClick 及关联对象无法被回收

如何检测内存泄露?

浏览器工具的Memory面板

  • Heap Snapshots:对比堆快照,查找未被释放的对象。
  • Allocation Timeline:实时跟踪内存分配,定位泄露点。

image.png

Node.js工具

  • --inspect参数:使用Chrome DevTools调试Node应用。
  • heapdump模块:生成堆快照分析内存。