日期:2014-05-16  浏览次数:20420 次

Javascript垃圾收集策略

Javascript具有自动拉圾收集机制,即执行环境会负责管理代码执行过程中使用的内存。这种垃圾收集的原理其实很简单:找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间)周期性地执行这一操作。那么如何判断一个变量不再被使用,有两种机制。


1.标记清除

Javascript中最常用的垃圾收集方式是标记清除(mark-and-sweep)。当变量进入环境时(如在函数中声明一个变量),就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当亦是离开环境时,则将其标记为“离开环境”。

可以采用任何方式标记变量。比如,可以通过翻转某个特殊的位来记录一个变量何时进入环境,或者使用一个“进入环境的”变量列表及一个“离开环境的”变量列表来跟踪哪个变量发生了变化。

垃圾收集器在运行的时候会给存储在内存中的所有变量及被环境中的变量引用的变量的标记。而在此后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这此变量了。最后,垃圾收集器完成内存清除的工作,销毁那些带标记的值并回收它们所占用的内存。

目前,IE,FF,Chrome,Safari,Opera的Javascript实现使用的都是标记清除式的垃圾收集策略,只不过垃圾收集的时间间隔互有不同。


2.引用计数

另一种不太常见的垃圾收集策略是引用计数 (reference counting)。引用计数的含义是跟踪记录每个值被引用的的次数。当声明了一个变量并将一个引用类型值赋给一个该变量时,则这个值的引用次数是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,若包含对这个值引用的变量又取得了另外一个值,则这个值的引用的次数减1。当这个值的引用次数变为0,则说明没有办法再访问该变量了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次运行时,它就会释放那些引用次数为0的值所点用的内存。不过,该策略存在一个严重的问题:循环引用。循环引用指的是A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。

function problem() {
    var A = new Object();
    var B = new Object();

    A.someOtherObject = B;
    B.anotherObject = A;
}

当采用引用计数策略的实现时,当函数执行完毕后,A和B还将继续存在,因为它们的引用次数永远不会是0.假如多次调用该函数,就会导致大量内存得不到回收。
IE中有一部分对象并不是原生的Javascript对象,如其中DOM,BOM中的对象就是使用C++以COM对象的形式实现的,而COM对象垃圾收集机制采用的是引用计数策略。故只要IE中涉及COM对象,就会存在循环引用问题。如下:

var el = document.getElementById('id');
var myObject = new Object();
myObject.el = el;
el.someObject = myObject;

为避免类似这样的循环引用问题,最好是在不使用它们的时候手工断开原生Javascript对象与DOM元素之间的连接。如下:

myObject.el= null;
el.someObject = null;

为解决上面的问题,IE9+把BOM和DOM对象都转换成了真正的Javascript对象。这样就避免了常见的内存泄漏问题。