说一下JS的垃圾回收机制?V8的新生代老生代垃圾回收了解吗?


theme: orange

JavaScript 垃圾回收机制与 V8 的新生代与老生代垃圾回收

JavaScript 是一种自动内存管理的语言,这意味着它有一个 垃圾回收(Garbage Collection, GC)机制,负责自动管理内存的分配与回收,确保不再使用的对象能够被清理,以避免内存泄漏。然而,垃圾回收的机制并不是一成不变的。现代 JavaScript 引擎(如 V8)在执行垃圾回收时,会根据对象的生命周期和存活情况采用不同的策略。

Snipaste_2024-11-28_10-15-30.png

1. JavaScript 的垃圾回收机制概述

JavaScript 的垃圾回收基于 引用计数标记清除 等算法。现代的 JavaScript 引擎(如 V8)大多使用 标记-清除(Mark-and-Sweep)分代垃圾回收(Generational Garbage Collection) 两种策略的结合。

  • 标记-清除:通过遍历对象图标记所有可达的对象,然后清理未被标记的对象。
  • 分代垃圾回收:将堆内存分为新生代和老生代,根据对象的存活周期采取不同的回收策略。这样可以优化垃圾回收过程,减少回收的停顿时间。

2. V8 的垃圾回收:新生代与老生代

V8 将堆内存分为多个区域,主要是 新生代老生代,每个区域有不同的垃圾回收策略。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.1 新生代(Young Generation)

新生代是 JavaScript 中刚创建的对象所在的内存区域。大多数对象在新生代内存中存活的时间都比较短,因此 V8 采用 较频繁的回收策略 来清理这些对象。新生代内存分为三个区域:

  1. From-space:存储需要回收的对象。
  2. To-space:存储存活的对象,回收过程中从 From-space 移动到 To-space。
  3. Survivor-space:存储存活的对象在新生代回收后的下一代对象。

V8 的新生代垃圾回收通过 复制算法(Copying Collector) 实现。每次垃圾回收时,会将存活的对象从 From-space 复制到 To-space,回收完成后,两个区域交换角色。

2.2 老生代(Old Generation)

老生代是存储长生命周期对象的内存区域。存活较长时间的对象会被提升到老生代。V8 在老生代上采用 标记-清除标记-压缩(Mark-Compact) 算法,这些算法比新生代的复制算法更为复杂且耗时,因为老生代的内存空间较大,垃圾回收周期较长。

3. V8 的垃圾回收流程

V8 的垃圾回收机制分为以下几个阶段:

  1. 标记阶段:从根对象开始,遍历所有可达对象并标记它们。
  2. 清除阶段:清理所有没有被标记的对象。
  3. 压缩阶段(对于老生代):如果有碎片化,V8 会对老生代进行压缩,将存活的对象移动到堆的一个连续区域,以避免内存碎片问题。

4. 代码示例与场景分析

我们通过一些简单代码示例来演示新生代与老生代垃圾回收的行为。

4.1 新生代垃圾回收示例
function createObject() {
  return { name: "Alice", age: 30 };
}

function testYoungGeneration() {
  let obj1 = createObject(); // 新生代对象
  let obj2 = createObject(); // 新生代对象
  
  obj1 = null;  // obj1 被销毁,可能触发新生代回收
  obj2 = null;  // obj2 被销毁,可能触发新生代回收
}

testYoungGeneration();

在这个示例中,我们创建了两个对象 obj1obj2,并将它们置为 null。这些对象是新生代对象,当 obj1obj2 不再引用时,V8 会执行垃圾回收,清理新生代内存中的无用对象。

4.2 老生代垃圾回收示例
function createLongLivingObject() {
  return { name: "Bob", age: 45, address: "1234 Elm Street" };
}

function testOldGeneration() {
  let obj1 = createLongLivingObject(); // 老生代对象
  let obj2 = createLongLivingObject(); // 老生代对象

  // 模拟对象存活较长时间
  setTimeout(() => {
    obj1 = null;  // obj1 被销毁
    obj2 = null;  // obj2 被销毁
  }, 10000);
}

testOldGeneration();

在这个例子中,我们模拟了创建长生命周期对象,并且将它们长时间保存在内存中。这些对象会逐渐迁移到老生代区域,因为它们存活的时间较长。老生代的垃圾回收机制会在一定时间后触发,并且可能执行更为复杂的标记-清除或者标记-压缩操作。

5. 内存泄漏与垃圾回收

尽管 JavaScript 引擎提供了垃圾回收机制,但开发者仍然需要小心内存泄漏。内存泄漏通常发生在以下几种情况:

  • 闭包引用:闭包中的引用可能导致对象无法被回收。
  • 全局变量:未清理的全局变量会一直存活,导致内存无法释放。
  • DOM 引用:如果事件处理程序或者 DOM 元素被不恰当地引用,可能会导致 DOM 节点无法被垃圾回收。
5.1 闭包导致的内存泄漏示例
function createClosure() {
  const largeObject = new Array(1000).fill('a');  // 创建一个大对象
  return function() {
    console.log(largeObject);
  };
}

const closure = createClosure();

在这个例子中,largeObject 是一个大的数组,由于闭包引用,它不会被垃圾回收,因为外部函数 createClosure 的作用域仍然持有 largeObject 的引用,导致内存泄漏。

5.2 DOM 引用导致的内存泄漏
let button = document.querySelector('button');

button.addEventListener('click', function() {
  console.log('Button clicked!');
});

// 如果在某些情况下没有移除事件监听器,
// 即使 button 元素被从 DOM 中移除,
// 它的引用仍然存在,因此不会被垃圾回收

在这段代码中,如果事件监听器没有被移除,即使 button 元素被从 DOM 中移除,它的引用仍然存在,导致内存泄漏。

6. 总结

对比 JavaScript 中 新生代(Young Generation)老生代(Old Generation) 的垃圾回收机制:

特性新生代(Young Generation)老生代(Old Generation)
内存区域存储生命周期较短的对象(通常是刚创建的对象)存储生命周期较长的对象(长期存活的对象)
垃圾回收策略使用 复制算法(Copying Collector):回收时将存活的对象从 From-space 移到 To-space使用 标记-清除(Mark-and-Sweep)标记-压缩(Mark-Compact):回收时标记所有活跃对象,并清理未标记的对象,压缩内存以消除碎片。
回收频率非常频繁,通常在对象短暂存活时就会被回收回收相对较少,通常在对象存活较长时间后才会触发回收
回收的代价相对较低,因为新生代内存较小且对象生命周期短,回收过程简单回收较复杂且耗时,因为老生代内存较大且对象生命周期长
存活对象迁移对象在新生代存活足够长时间后,会被提升到老生代一旦对象存活时间过长,会被移动到老生代,继续占用内存
内存区域划分分为 From-spaceTo-spaceSurvivor-space存储在较大的内存区域中,没有类似新生代的空间划分
回收过程每次回收时将存活对象复制到另一个区域,避免碎片化问题。需要进行标记、清除和可能的压缩,处理内存碎片。
典型对象类型临时变量、短期使用的对象长期使用的对象,例如缓存、全局变量、DOM 节点等

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

主要区别总结:

  1. 新生代 回收频繁,采用 复制算法,且内存较小,因此回收代价低,回收过程快速。
  2. 老生代 对象生命周期较长,采用更复杂的 标记-清除标记-压缩 算法,回收较少但代价较高。

这种分代回收策略是为了优化内存管理,减少频繁回收带来的性能损失,尤其在大型应用中效果显著。

1再见.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值