浅谈浏览器垃圾回收机制

垃圾回收机制原理

由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript 程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript 的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。

JS 的垃圾回收机制是为了以防内存泄漏,内存泄漏的含义就是当已经不需要某块内存时这块内存还存在着,垃圾回收机制就是间歇的不定期的寻找到不再使用的变量,并释放掉它们所指向的内存

垃圾回收原理浅析

现在各大浏览器通常用采用的垃圾回收有两种方法:标记清除、引用计数。

标记清除(mark and sweep)

大部分浏览器以此方式进行垃圾回收,当变量进入执行环境(函数中声明变量,执行时)的时候,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”,在离开环境之后还有的变量则是需要被删除的变量。标记方式不定,可以是某个特殊位的反转或维护一个列表等。
   垃圾收集器给内存中的所有变量都加上标记,然后去掉环境中的变量以及被环境中的变量引用的变量的标记。在此之后再被加上的标记的变量即为需要回收的变量,因为环境中的变量已经无法访问到这些变量。

工作原理

是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时, 则将其标记为“离开环境”。标记“离开环境”的就回收内存

工作流程

垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记 去掉环境中的变量以及被环境中的变量引用的变量的标记 再被加上标记的会被视为准备删除的变量
垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间

引用计数

另一种不太常见的垃圾回收策略是引用计数。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是 1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减 1。当这个引用次数变成 0 时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为 0 的值所占的内存。
该方式会引起内存泄漏的原因是它不能解决循环引用的问题:

1
2
3
4
5
6
function sample() {
var a = {};
var b = {};
a.prop = b;
b.prop = a;
}

这种情况下每次调用 sample()函数,a 和 b 的引用计数都是 2,会使这部分内存永远不会被释放,即内存泄漏。

低版本 IE 中有一部分对象并不是原生 JS 对象。例如,其 BOM 和 DOM 中的对象就是使用 C++以 COM(Component Object Model)对象的形式实现的,而 COM 对象的垃圾收集机制采用的就是引用计数策略。

因此即使 IE 的 js 引擎是用的标记清除来实现的,但是 js 访问 COM 对象如 BOM,DOM 还是基于引用计数的策略的,也就是说只要在 IE 中设计到 COM 对象,也就会存在循环引用的问题。

当一个 DOM 元素和一个原生的 js 对象之间的循环引用时:

1
2
3
4
5
6
7
8
9
10
var ele = document.getElementById("eleId");

var obj = {};
obj.property = ele;
ele.property = obj;

/* 添加 */
obj.property = null;
ele.property = null;
/* 即可解除原生JS对象与DOM元素之间的连接 */

当闭包中创建循环引用时:

1
2
3
4
5
6
window.onload = function outerFunction() {
var obj = document.getElementById("eleId");
obj.onclick = function innerfunction() {
console.log(obj.id);
};
};

上面这个代码创建了一个作为 obj 元素处理程序的闭包,而这个闭包则又创建了一个循环引用。obj 引用了 document.getElementById("element"),而 document.getElementById("eleId")的 onclick 方法会引用包括 obj 以内的外部环境中的变量,所谓“外部环境”包括了包含函数的整个活动对象,所以一定会包括 obj(即使闭包没有对 obj 进行直接的引用,例如上文程序中没有 obj.id 出现,包含函数的活动对象(obj)中也依旧会保存一个引用)。

可以改成下面这个:

1
2
3
4
5
6
7
8
window.onload = function outerFunction() {
var obj = document.getElementById("element");
var id = obj.id; //将obj副本保存于变量id中,则不会使obj元素处理程序的闭包创建循环引用
obj.onclick = function innerfunction() {
console.log(id);
};
ele = null; //手动断开 obj 对 document.getElemengById("element")的引用
};

介于 js 的垃圾回收机制,则建议如果当引用完大的对象后将其置为 null