常規(guī)循環(huán)引用內(nèi)存泄漏和Closure內(nèi)存泄漏
要了解javascript的內(nèi)存泄漏問(wèn)題,首先要了解的就是javascript的GC原理。
我記得原來(lái)在犀牛書(shū)《JavaScript: The Definitive Guide》中看到過(guò),IE使用的GC算法是計(jì)數(shù)器,因此只碰到循環(huán) 引用就會(huì)造成memory leakage。后來(lái)一直覺(jué)得和觀察到的現(xiàn)象很不一致,直到看到Eric的文章,才明白犀牛書(shū)的說(shuō)法沒(méi)有說(shuō)得很明確,估計(jì)該書(shū)成文后IE升級(jí)過(guò)算法吧。在IE 6中,對(duì)于javascript object內(nèi)部,jscript使用的是mark-and-sweep算法,而對(duì)于javascript object與外部object(包括native object和vbscript object等等)的引用時(shí),IE 6使用的才是計(jì)數(shù)器的算法。
Eric Lippert在http://blogs.msdn.com/ericlippert/archive/2003/09/17/53038.aspx一文中提到IE 6中JScript的GC算法使用的是nongeneration mark-and-sweep。對(duì)于javascript對(duì)算法的實(shí)現(xiàn)缺陷,文章如是說(shuō):
"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. "
也就是說(shuō),IE 6對(duì)于純粹的Script Objects間的Circular References是可以正確處理的,可惜它處理不了的是JScript與Native Object(例如Dom、ActiveX Object)之間的Circular References。
所以,當(dāng)我們出現(xiàn)Native對(duì)象(例如Dom、ActiveX Object)與Javascript對(duì)象間的循環(huán)引用時(shí),內(nèi)存泄露的問(wèn)題就出現(xiàn)了。當(dāng)然,這個(gè)bug在IE 7中已經(jīng)被修復(fù)了[http://www.quirksmode.org/blog/archives/2006/04/ie_7_and_javasc.html]。
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp 中有個(gè)示意圖和簡(jiǎn)單的例子體現(xiàn)了這個(gè)問(wèn)題:
<html> <head> <script language = " JScript "> var myGlobalObject; function SetupLeak() // 產(chǎn)生循環(huán)引用,因此會(huì)造成內(nèi)存泄露 { // First set up the script scope to element reference myGlobalObject = document.getElementById("LeakedDiv"); // Next set up the element to script scope reference document.getElementById(" LeakedDiv ").expandoProperty = myGlobalObject; } function BreakLeak() // 解開(kāi)循環(huán)引用,解決內(nèi)存泄露問(wèn)題 { document.getElementById( " LeakedDiv " ).expandoProperty = null ; } </script> </head> <body onload = "SetupLeak()" onunload = "BreakLeak()"> <div id = "LeakedDiv" ></div> </body> </html>
上面這個(gè)例子,看似很簡(jiǎn)單就能夠解決內(nèi)存泄露的問(wèn)題?上У氖,當(dāng)我們的代碼中的結(jié)構(gòu)復(fù)雜了以后,造成循環(huán)引用的原因開(kāi)始變得多樣,我們就沒(méi)法那么容易觀察到了,這時(shí)候,我們必須對(duì)代碼進(jìn)行仔細(xì)的檢查。
尤其是當(dāng)碰到Closure,當(dāng)我們往Native對(duì)象(例如Dom對(duì)象、ActiveX Object)上綁定事件響應(yīng)代碼時(shí),一個(gè)不小心,我們就會(huì)制造出Closure Memory Leak。其關(guān)鍵原因,其實(shí)和前者是一樣的,也是一個(gè)跨javascript object和native object的循環(huán)引用。只是代碼更為隱蔽,這個(gè)隱蔽性,是由于javascript的語(yǔ)言特性造成的。但在使用類似內(nèi)嵌函數(shù)的時(shí)候,內(nèi)嵌的函數(shù)有擁有一個(gè)reference指向外部函數(shù)的scope,包括外部函數(shù)的參數(shù),因此也就很容易造成一個(gè)很隱蔽的循環(huán)引用,例如:
DOM_Node.onevent ->function_object.[ [ scope ] ] ->scope_chain ->Activation_object.nodeRef ->DOM_Node。
[http://msdn.microsoft.com/library/default.asp?url=/library/en-us/IETechCol/dnwebgen/ie_leak_patterns.asp]有個(gè)例子極深刻地顯示了該隱蔽性:
<html> <head> <script language = "JScript"> function AttachEvents(element) { // This structure causes element to ref ClickEventHandler // element有個(gè)引用指向函數(shù)ClickEventHandler() element.attachEvent("onclick", ClickEventHandler); function ClickEventHandler(){ // This closure refs element // 該函數(shù)有個(gè)引用指向AttachEvents(element)調(diào)用Scope,也就是執(zhí)行了參數(shù)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>
出處:藍(lán)色理想
責(zé)任編輯:bluehearts
上一頁(yè) 下一頁(yè) 關(guān)于Javascript的內(nèi)存泄漏問(wèn)題 [2]
◎進(jìn)入論壇網(wǎng)頁(yè)制作、WEB標(biāo)準(zhǔn)化版塊參加討論,我還想發(fā)表評(píng)論。
|