js里的垃圾回收机制

垃圾回收机制是什么?

垃圾回收机制(Garbage Collection) 简称 GC

JavaScript 的内存管理是自动的、无形的。我们创建的原始值、对象、函数……这一切都会占用内存

当代码执行完毕的时候,JS引擎也会自动地将你的程序,所占用的内存清理掉

所谓垃圾回收机制就是清理内存的方式

垃圾回收机制是怎样进行的?

可达性

简而言之,“可达”值是那些以某种方式可访问或可用的值。它们一定是存储在内存中的,是存在的。就不会被清除,反之就会被垃圾回收机制清除掉

那么它是如何找到“垃圾”,并且把它清除掉的呢?

主要有两种内存回收策略

1.引用计数法

它的策略是跟踪记录每个变量值被使用的次数

这是最初级的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。

  • 当声明了一个变量并且将一个引用类型赋值给该变量的时候这个值的引用次数就为 1

  • 如果同一个值又被赋给另一个变量,那么引用数加 1

  • 如果该变量的值被其他的值覆盖了,则引用次数减 1

  • 当这个值的引用次数变为 0 的时候,说明没有变量在使用,这个值没法被访问了,回收空间,垃圾回收器会在运行的时候清理掉引用次数为 0 的值占用的内存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    let o = {
    a: {
    b: 2,
    },
    };
    // 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量 o
    // 很显然,没有一个可以被垃圾收集

    let o2 = o; // o2 变量是第二个对“这个对象”的引用

    o = 1; // 现在,“这个对象”只有一个 o2 变量的引用了,“这个对象”的原始引用 o 已经没有

    let oa = o2.a; // 引用“这个对象”的 a 属性
    // 现在,“这个对象”有两个引用了,一个是 o2,一个是 oa

    o2 = "yo"; // 虽然最初的对象现在已经是零引用了,可以被垃圾回收了
    // 但是它的属性 a 的对象还在被 oa 引用,所以还不能回收

    oa = null; // a 属性的那个对象现在也是零引用了
    // 它可以被垃圾回收了

    但是无法解决对象之间的循环引用,会导致内存泄漏

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function fn(){ // fn引用次数为1,因为window.fn = fn,会在window=null即浏览器关闭时回收
    let A = new Object() // A: 1
    let B = new Object() // B: 1
    A.b = B // B: 2
    B.a = A // A: 2
    }
    // A对象中引用了B,B对象中引用了A,两者引用计数都不为0,永远不会被回收。
    // 若执行无限多次fn,那么内存将会被占满,程序宕机
    fn();
    // 还有就是这种方法需要一个计数器,这个计数器可能要占据很大的位置,因为我们无法知道被引用数量的多少。

2.标记清除法(mark-and-sweep)

  • 从2012年起,所有浏览器都使用了标记清除法。
  • 目前主流浏览器都是使用标记清除式的垃圾回收策略,只不过收集的间隔有所不同。

也解决了引用计数无法清除循环引用导致的内存泄漏

该策略分为Mark(标记)Sweep(清除)两个阶段。

Mark阶段:

  • 运行时,将内存中的所有变量标记为0(垃圾)
  • 从各个根对象遍历,将非垃圾变量标记为1

Sweep阶段:

  • 将所有标记为0的变量的内存释放

定期执行以下“垃圾回收”步骤:

  • 垃圾收集器找到所有的根,并“标记”(记住)它们。
  • 然后它遍历并“标记”来自它们的所有引用。
  • 然后它遍历标记的对象并标记 它们的 引用。所有被遍历到的对象都会被记住,以免将来再次遍历到同一个对象。
  • ……如此操作,直到所有可达的(从根部)引用都被访问到。
  • 没有被标记的对象都会被删除。

我们可以清楚地看到右侧有一个“无法到达的岛屿”。现在我们来看看“标记和清除”垃圾收集器如何处理它。

第一步标记所有的根:

然后,我们跟随它们的引用标记它们所引用的对象:

……如果还有引用的话,继续标记:

现在,无法通过这个过程访问到的对象被认为是不可达的,并且会被删除。

我们还可以将这个过程想象成从根溢出一大桶油漆,它流经所有引用并标记所有可到达的对象。然后移除未标记的。

为什么要进行垃圾回收?

解决动态内存管理的问题,确保程序在运行时能够高效地分配和释放内存资源,从而避免内存泄漏和内存碎片化,提高程序的稳定性和性能

总结:

  • 垃圾回收是自动完成的,我们不能强制执行或是阻止执行。
  • 当对象是可达状态时,它一定是存在于内存中的。
  • 被引用与可访问(从一个根)不同:一组相互连接的对象可能整体都不可达,正如我们在上面的例子中看到的那样。

该博客为个人前端学习过程中的个人总结,难免会有一些错误和一些没有注意到的一些疏漏,欢迎指正错误!

参考文章

MDN-垃圾回收机制

JavaScript现代教程-垃圾回收

js的垃圾回收机制

「硬核JS」你真的了解垃圾回收机制吗