Skip to content

垃圾回收机制

什么是 GC

提示

在 JavaScript 中,"gc"代表垃圾回收(Garbage Collection) 。垃圾回收是一种自动内存管理机制,用于检测和清除不再使用的对象,以释放内存空间。当一个对象不再被引用时,垃圾回收器会将其标记为垃圾,然后在适当的时候清除这些垃圾对象,并将内存回收给系统以供其他对象使用。垃圾回收的目的是减少内存泄漏和提高程序的性能。

在 C 和 C++等语言中,开发者需要手动管理内存。虽然这种方式可以给开发者更大的灵活性和控制性,但也容易引起内存泄漏和悬挂指针等问题。而在 JavaScript 中,垃圾回收是由 JavaScript 引擎自动执行的,开发者无需手动管理内存。

垃圾产生的原因

JavaScript中,垃圾是指不再被程序所使用的对象或数据。以下是垃圾产生的一些常见情况:

  1. 对象不再被引用:当一个对象不再被任何变量或属性引用时,它就成为垃圾。例如,当一个函数执行完毕后,其中创建的局部变量将成为垃圾,因为它们无法再被访问到。

  2. 对象之间形成循环引用:当两个或多个对象相互引用,并且它们之间没有外部引用时,它们将成为垃圾。这种情况下,即使这些对象不再被任何其他代码引用,它们也无法被垃圾回收器清除。

  3. 动态创建的对象没有被及时销毁:如果在代码中频繁地创建新的对象,但没有及时销毁这些对象,就会产生垃圾。特别是在循环或递归等情况下,如果没有正确地释放内存,垃圾会不断积累。

  4. 内存泄漏:当代码错误地保留了不再需要的对象引用时,就会发生内存泄漏。这通常是由于没有合理使用闭包、未解除定时器或忘记解除事件监听等引起的。

垃圾回收算法

引用计数(Reference Counting)

提示

定义:引用计数(Reference Counting)算法通过跟踪每个对象被引用的次数来确定对象是否为垃圾。

每个对象都有一个引用计数器,引用计数的过程如下:

  1. 当一个对象被创建时,其引用计数器初始化为 1。
  2. 当该对象被其他对象引用时,引用计数器加 1。
  3. 当该对象不再被其他对象引用时,引用计数器减 1。
  4. 当引用计数器减至 0 时,意味着该对象不再被引用,可以被垃圾收集器回收。
js
// 创建一个对象
let obj = { name: "test" };
// 创建一个引用指向对象
let ref1 = obj; //引用计数+1 1

// 创建另一个引用指向对象
let ref2 = obj; //引用计数+1 2

// 引用失效
ref1 = null; //引用计数-1 1
ref2 = null; //引用计数-1 0

// 引用计数为0,对象可以被回收

优势:

  • 实时回收:引用计数可以在对象不再被引用时立即回收,不需要等待垃圾收集器的运行。这可以减少内存占用和提高程序的性能。
  • 简单高效:引用计数是一种简单的垃圾收集算法,实现起来相对容易,不需要复杂的算法和数据结构。

存在的问题:

  • 循环引用:当两个或多个对象相互引用时,它们的引用计数都不为零,即使它们已经不再被其他对象引用,也无法被回收。这导致内存泄漏,因为这些对象仍然占据内存空间,却无法被释放。
js
const objA = {};
const objB = {};
//`objA`和`objB`相互引用,没有其他对象引用它们。
objA.ref = objB; //objA引用objB
objB.ref = objA; //objB引用objA
  • 计数开销:维护每个对象的引用计数需要占用额外的内存空间,而且每次添加、删除引用都需要更新计数,增加了额外的开销。

标记-清除(Mark and Sweep)

提示

定义:标记-清除(Mark and Sweep)算法通过标记不再使用的对象,然后清除这些对象的内存空间,以便后续的内存分配使用。

它分为两个阶段:标记阶段和清除阶段。

  1. 标记阶段:在标记阶段,垃圾回收器会对内存中的所有对象进行遍历,从根对象开始(通常是全局对象)递归地遍历对象的引用关系。对于每个被访问到的对象,垃圾回收器会给它打上标记,表示该对象是可达的,即不是垃圾。这个过程确保了所有可达对象都会被标记。
  2. 清除阶段:在清除阶段,垃圾回收器会遍历整个内存,对于没有标记的对象,即被判定为垃圾的对象,会被立即回收,释放内存空间。这样,只有被标记的对象会被保留在内存中,而垃圾对象会被清除。

优势:

  • 简单有效:标记-清除算法相对简单,容易实现。它可以准确地找到不再被引用的对象,并回收内存
  • 处理循环引用:标记-清除算法能够处理循环引用的情况。当对象之间存在循环引用时,即使它们不再被任何其他对象引用,引用计数算法也无法将它们识别为垃圾,而标记-清除算法可以通过遍历的方式找到并清除这些对象。

存在的问题:

  • 垃圾回收过程中的停顿:标记-清除算法会暂停程序的执行,进行垃圾回收操作。当堆中对象较多时,可能会导致明显的停顿,影响用户体验。
  • 内存碎片化:标记-清除算法会在回收过程中产生大量的不连续的、碎片化的内存空间。这可能导致后续的内存分配难以找到足够大的连续内存块,从而使得内存的利用率降低。

标记-整理(Mark and Compact)

提示

定义:标记整理(Mark and Compact)可以看作是标记清除的增强操作,他在标记阶段的操作和标记清除一致,但是清除阶段会先执行整理,移动对象位置,对内存空间进行压缩。

它分为三个阶段:标记阶段、整理阶段和清除阶段。

  1. 标记阶段:将所有活动对象进行标记。
  2. 整理阶段:将内存中的活动对象移动到一端,使得空闲空间连续,并且没有碎片化。
  3. 清除阶段:将未标记的对象进行清除操作,并回收其占用的内存空间。

优势:

  • 解决了标记-清除算法的碎片化问题:标记-整理算法在清除阶段会将标记的对象整理到内存的一端,从而解决了标记-清除算法产生的碎片化问题。这样可以使得内存空间得到更好的利用,减少了空间的浪费。
  • 处理循环引用:标记-整理算法也能够处理循环引用的情况。

存在的问题:

  • 垃圾回收过程中的停顿:标记-整理算法同样会暂停程序的执行,进行垃圾回收操作。当堆中对象较多时,可能会导致明显的停顿,影响用户体验。