「前端面试100问」之JavaScript 内存管理和垃圾回收机制

1,520 阅读5分钟

内存管理的主要目标是在需要时为系统提供动态分配的内存,然后释放包含不再使用的对象的内存。 诸如C,C ++之类的语言具有诸如malloc()之类的用于内存分配的原始函数,其中某些高级语言(如JavaScript)内置了垃圾收集器来完成此工作。 它跟踪程序的内存分配,并分享程序是否不再使用分配的内存,然后将其自动释放。 但是这种算法无法完全决定是否需要内存。 因此,对于程序员而言,理解并确定特定代码段是否需要内存非常重要。

接下来我们一起来了解垃圾回收如何在JavaScript中工作。

垃圾收集

JavaScript引擎的垃圾收集器基本上会寻找从内存中删除的无法访问的对象。 这篇文章解释以下两种常见的垃圾收集算法:

  • 引用计数法
  • 标记清除法

引用计数法

这是一个简单的垃圾收集算法。 该算法查找那些没有被引用的对象,如果对象没有附加引用,则可以进行垃圾回收。

var obj1 = {
    property1: {
        subproperty1: 20
    }
};

创建一个如上例所示的对象,以了解该算法。

obj1指向了一个对象,其property1中还引用了另外一个对象。 由于obj1引用了property1指向的对象,因此此对象是不会被垃圾回收器进行回收的。

var obj2 = obj1;
obj1 = "some random text"

现在,obj2也具有了对obj1所引用的同一对象的引用,但是后来obj1的值被更新为“some random text”,所以现在只有obj2对引用着这个对象。

var obj_property1 = obj2.property1;

接下来,obj_property1引用obj2.property1,该对象也包含一个对象。 因此,该对象现在具有两个引用,如下所示:

  • 作为obj2的属性
  • 变量obj_property1
obj2 = "some random text"

obj2 被更新为 “some random text” 之后,其也不再引用这个对象。 因此,似乎这个对象没有了其余的引用,因此可以被垃圾回收了。 但这可能是错误的说法,因为obj_property1具有obj2.property1的引用。 因此也不会被垃圾收集。

obj_property1 = null;

现在,当我们从obj_property1中删除引用时,原来位于obj1中的对象已完全没有引用。 因此,现在可以对其进行垃圾收集了。

该算法在哪些代码中会失效?

function example() {
     var obj1 = {
         property1 : {
              subproperty1: 20
         }
     };
     var obj2 = obj1.property1;
     obj2.property1 = obj1;
     return 'some random text'
}
example();

在此,引用计数算法不会在函数调用后从内存中删除obj1obj2,因为这两个对象相互引用

标记清除法

标记清除法会查找从根(JavaScript的全局对象)无法访问的对象。

  • 该算法克服了引用计数算法的局限性。

  • 没有引用的对象将是不可访问的,反之亦然。

var obj1 = {
     property1: 35
}

如上所示,我们可以看到创建的对象obj1如何从ROOT可达。

obj1 = null

现在,当我们将obj1的值设置为null时,该对象不再可以从ROOT到达,因此它将被进行垃圾回收。

预备知识

javascript中的内存管理是自动执行的,而且是不可见的
可达性:以某种方式可以访问或者可以用的值,被保证存储在内存中
根:有一些基本的固有可达值,由于显而易见的原因无法删除
    本地函数的局部变量和参数
    当前嵌套调用链上的其他函数的变量和参数
    全局变量
    其它...
可访问性:如果引用或引用链可以从根访问其他值,则认为该值是可访问的

标记-清除法 原理:

基本的垃圾回收算法称为“标记-清除”
垃圾回收器获取根并“标记”它们
访问并标记所有来自根的引用
访问标记的对象并标记其引用。
以此类推,直到有未访问的引用为止
除了标记对象,所有对象都被删除

让我们通过查看以下实例来尝试和理解:

如上所示,这就是对象结构的样子。 我们可以注意到从根目录无法访问的对象,但让我们尝试了解“标记并清除”算法在这种情况下的工作方式。

算法开始标记它从根开始可达的对象。 在上图中,我们可以注意到在对象上标记的绿色圆圈。 以便将对象标识为从根可访问。

那些从根目录无法访问的未标记的对象,它们将被垃圾收集。

注意

「垃圾的定义」

没有被引用的对象或者有对象引用,但对象之间为相互引用,根访问不到

如上图所示,尽管对象被引用了,但整体仍然是孤立的,最后仍然会被视为『垃圾』,并进行回收。

局限性

必须使对象明确不可访问,才会进行垃圾收集。

自2012年以来,JavaScript引擎针对引用计数垃圾收集算法进行了修改。

补充:

JavaScript 引擎在垃圾回收方面的优化:

分代回收:对象分为“新对象”和“旧对象”,新对象出现,工作结束后就会被清理干净,存在时间长的对象,就会很少接受检查

增量回收:将垃圾回收分解为多个部分,分别执行

空闲时间收集:垃圾回收器只在CPU空闲时运行

谢谢阅读。

参考