JS 当中的垃圾回收算法

111 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 3 天,点击查看活动详情

JS 当中的垃圾回收算法

什么是垃圾回收?为什么需要垃圾回收。

像 C 语言这样的底层语言一般都会有底层的内存管理接口,比如free()malloc()来回收和分配内存。但是 JavaScript 这样的高级语言却没有这样的接口,因为 JavaScript 的内存分配是在变量进行定义的时候进行分配的,而不是在使用的时候,由程序员手动分配的。因此,在 JavaScript 当中,内存的分配与回收有一个专门的垃圾回收管理器。

不管是上面程序设计语言,内存的生命周期都类似:

  • 分配需要的内存
  • 对分配的内存进行操作
  • 回收使用完的内存

所有的语言在第二点都有一样的,但是第一点和第三点却大不相同,像C语言,他是需要手动分配,而像javajavascript他们会在变量定义的时候自定分配大小,我们称其为隐式分配。

在 JavaScript 当中,内存的回收都是依赖于垃圾回收器,它的主要工作是跟踪内存的分配与使用,以便当分配的内存不再使用的时候,自动释放它。这只是一个近似的过程,因为要知道是否仍然需要某块内存是无法判定的(无法通过某种算法解决)。

引用计数垃圾回收算法

引用计数垃圾回收算法是早期浏览器的一种垃圾回收算法,它主要依赖于引用的概念。在内存管理环境当中,一个对象如果有另外一个对象的权限(隐式或者显示),叫做一个对象引用另外一个引用。比如一个对象对其原型链的引用就是隐式引用,一个对象对其属性的引用就是显示引用。

而引用计数则通过判断一个对象是否被另外一个对象所引用来判断是否需要对这个对象进行回收。如果那个对象没有被任何对象所引用,那么就可以回收,如果有被其他对象引用,那么就无法被回收。听上去感觉很好对不对?但是,你似乎忘记了一件事情,对象之间是可以相互引用的比如下面:

let o = {
  a: 18,
};

let j = {
    b = 20,
}

o.a = b;
j.b = o;

向上面这样,oj都无法被回收,因为两者始终都有一个对象在引用他们,他们的引用也始终不为 0,所以在程序运行期间,所占用的内存就不会被释放。IE 6, 7 使用引用计数方式对 DOM 对象进行垃圾回收。该方式常常造成对象被循环引用时内存发生泄漏:

var div;
window.onload = function () {
  div = document.getElementById("myDivElement");
  div.circularReference = div;
  div.lotsOfData = new Array(10000).join("*");
};

现在引用计数回收算法已经被淘汰了,因为它会在循环引用的时候造成内存泄漏导致程序运行崩溃。

标记清除垃圾回收算法

标记清除算法把对象是否不再需要简化定义为对象是否可以获得

简单来说,就是是否可以在全局作用域当中找到某个对象,然后通过某个对象找到其他对象的引用,最终形成一颗树。我们家假定设置一个叫作根(root)的节点对象,在 javascript 当中,根就是全局对象,然后我们通过全局对象寻找全局对象所引用的对象,然后....,最终收集到所有可以获得的对象,和无法获得到的对象。

这样做的好处是,当一个对象没有被任何引用的话,就是零引用对象,而零引用对象总是不可获得的,但是包含引用的,也不一定是一定可以被获得的。比如之前我们所讨论的关于循环引用的问题。在标记清除垃圾回收算法当中,循环引用的对象无法通过全局对象的递归来找到,因此循环引用的对象也是无法获得的。这样我们就解决了引用计数算法的缺点。