JavaScript 常规循环引用内存泄漏和Closure内存泄漏

要了解javascript的内存泄漏问题,首先要了解的就是javascript的GC原理。

我记得原来在犀牛书《JavaScript: The Definitive Guide》中看到过,IE使用的GC算法是计数器,因此只碰到循环 引用就会造成memory leakage。后来一直觉得和观察到的现象很不一致,直到看到Eric的文章,才明白犀牛书的说法没有说得很明确,估计该书成文后IE升级过算法吧。在IE 6中,对于javascript object内部,jscript使用的是mark-and-sweep算法,而对于javascript object与外部object(包括native object和vbscript object等等)的引用时,IE 6使用的才是计数器的算法。

Eric Lippert在[url]Fabulous Adventures In Coding | Microsoft Learn 6中JScript的GC算法使用的是nongeneration mark-and-sweep。

对于javascript对算法的实现缺陷,文章如是说:

"The benefits of this approach are numerous, but the principle benefit is that circular references are not leaked unless the circular reference involves an object not owned by JScript. "

也就是说,IE 6对于纯粹的Script Objects间的Circular References是可以正确处理的,可惜它处理不了的是JScript与Native Object(例如Dom、ActiveX Object)之间的Circular References。

所以,当我们出现Native对象(例如Dom、ActiveX Object)与Javascript对象间的循环引用时,内存泄露的问题就出现了。

当然,这个bug在IE 7中已经被修复了[[url]http://www.quirksmode.org/blog/archives/2006/04/ie_7_and_javasc.html[/url]]。

[url]Technical documentation | Microsoft Learn 中有个示意图和简单的例子体现了这个问题:[code]


<body onload = " SetupLeak() "  onunload = " BreakLeak() " >
<div id = " LeakedDiv " ></div>
</body>
</html>[/code]上面这个例子,看似很简单就能够解决内存泄露的问题。可惜的是,当我们的代码中的结构复杂了以后,造成循环引用的原因开始变得多样,我们就没法那么容易观察到了,这时候,我们必须对代码进行仔细的检查。

尤其是当碰到Closure,当我们往Native对象(例如Dom对象、ActiveX Object)上绑定事件响应代码时,一个不小心,我们就会制造出Closure Memory Leak。

其关键原因,其实和前者是一样的,也是一个跨javascript object和native object的循环引用。只是代码更为隐蔽,这个隐蔽性,是由于javascript的语言特性造成的。但在使用类似内嵌函数的时候,内嵌的函数有拥有一个reference指向外部函数的scope,包括外部函数的参数,因此也就很容易造成一个很隐蔽的循环引用,例如:
DOM_Node.onevent ->function_object.[ [ scope ] ] ->scope_chain ->Activation_object.nodeRef ->DOM_Node。

[[url]Technical documentation | Microsoft Learn < html >
< head >
< script language = " JScript " >

function  AttachEvents(element)

{
// This structure causes element to ref ClickEventHandler //element有个引用指向函数ClickEventHandler()
element.attachEvent( " onclick " , ClickEventHandler);

function  ClickEventHandler()
{
    //  This closure refs element  //该函数有个引用指向AttachEvents(element)调用Scope,也就是执行了参数element。
            
}

}

function SetupLeak()
{
// The leak happens all at once
AttachEvents(document.getElementById( " LeakedDiv " ));
}

</ script >
</ head >

< body onload = " SetupLeak() "  onunload = " BreakLeak() " >
< div id = " LeakedDiv " ></ div >
</ body >
</ html >[/code]还有这个例子在IE 6中同样原因会引起泄漏[code]function  leakmaybe() {
var  elm  =  document.createElement( " DIV " );
elm.onclick  =   function () {
    return   2   +   2 ;
}

}

for ( var i = 0 ; i 10000 ; i ++ ) {
leakmaybe();
}[/code]