首页 > 技术文章 > Javascript垃圾收集机制

hyqdvd 2016-02-07 13:47 原文

Javascript具有自动垃圾收集机制,执行环境会负责管理代码执行过程中使用的内存。而在C和C++之类的语言中,开发人员的一项基本任务就是手工跟踪内存的使用情况,这就造成了许多问题。在Javascript中,开发人员不必关系内存使用问题,内存分配完全是自动管理。这种垃圾回收机制很简单:找出不再继续使用的变量,然后释放内存。为此,垃圾收集器会按照固定时间周期性地执行这一操作。

下面来分析一下函数中的局部变量的正常周期。局部变量只在函数执行过程中存在,而在这个过程中,解释器会为局部变量在栈或堆内存上分配相应的空间,以便存储它们的值。然后在函数中使用这些变量直到函数结束,解释器就会回收内存。垃圾收集器对于不再使用的变量打上标记,以便将来回收内存。用于标示无用变量的策略可能因为实现而不同,但具体到浏览器中,通常有两个策略。

1、标记清除(mark-and-sweep)

Javascript最常用的垃圾收集方式是标记清除。当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”,当变量离开环境时,解释器将其标记为“离开环境”。可以使用任何方式来标记变量。比如,可以通过翻转某个特殊的位来记录一个变量何时进入环境,或者使用一个“进入环境”的变量列表以及一个“离开环境”的变量列表来跟踪哪个变量发生变化。说到底,如何标记变量不重要,关键在于采取什么策略。

2、引用计数(reference counting)

引用计数的含义是跟踪记录每个值被引用的次数。当声明一个变量并将一个引用类型值赋给该变量,则这个引用类型值得引用次数为1.如果同一个值又被赋给另一个变量,则引用次数加1。相反,如果另一个值被赋给了该变量,那么原来的值引用次数减1.当这个值的引用次数为0时,解释器会将这个值回收。

Netscape Navigator 3.0是最早使用引用计数方式的浏览器,但很快它就遇到了一个问题:循环引用。循环引用指的是A对象包含一个指向B的指针,B对象包含一个指向A的引用,如下面的例子:

function problem(){

var objectA = new Object();

var objectB = new Object();

objectA.attri1 = objectB;

objectB.attri2 = objectA;

}

这个例子中,objectA和objectB通过各自的属性相互引用,也就是说,这两个对象的引用次数都是2。在采用标记清除的策略时,由于函数执行之后,这两个对象都离开了作用域,因此相互引用不是问题。但是在引用计数策略中,当函数执行完,objectA和objectB还将继续存在,它们的引用次数永远不会是0.加入这个函数被调用了很多次,会导致大量内存不能回收。

然而,引用计数的麻烦不仅仅只有以上部分。IE中有一部分对象并不是原生的javascript对象。例如,BOM和DOM中的对象就是使用C++以COM(组件对象模型)对象的形式实现的,而COM对象的垃圾收集机制采用的是引用计数策略。因此,即使IE的javascript引擎是使用标记清除策略实现的,但javascript访问的COM对象依然是基于引用计数的。换句话说,只要IE涉及COM对象,就会存在循环引用的问题。下面的例子展示了COM对象和javascript对象相互引用产生的问题:

var element = document.getElementById('element');

var myobject = new Object();

myobject.ele = element;

element.someobject = myobject;

这个例子中存在一个javascript对象和一个DOM对象,由于循环引用,两个对象不会被回收。

为了避免类似的循环引用问题,最好是在不使用它们时手动断开两个对象之间的连接,如下:

myobject.ele = null;

element.someobject = null;

为了解决上述问题,IE9把BOM和DOM对象都转换为真正的javascript对象。这样就避免了两种垃圾收集策略并存导致的问题。

 

使用具备垃圾收集机制的语言编写程序时,开发人员不必关心内存管理问题。操作系统给浏览器分配的可用内存通常比桌面应用程序要少一些。这样做的目的是出于安全方面的考虑,目的是防止网页使用太多内存导致系统崩溃。内存限制问题不仅影响给变量分配内存,同时还会影响调用栈以及一个线程中能够同时执行的语句数量。

因此,确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为代码执行保存必要的数据。一旦数据不可用,将其值设置为null,这个方法叫做解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象的属性。局部变量会在离开执行环境时自动解除引用,如下:

function createperson(name){

var locationperson = new Object();

locationperson.name = name;

return locationperson;

}

var globalperson = createperson('jim');

globalperson = null;

全局变量需要手动解除引用!

注意,解除引用不意味着自动回收内存。解除引用的作用在于让值脱离执行环境,以便垃圾收集器回收。

 

推荐阅读