理解并解決IE的內存泄漏方式_第1頁
理解并解決IE的內存泄漏方式_第2頁
理解并解決IE的內存泄漏方式_第3頁
理解并解決IE的內存泄漏方式_第4頁
理解并解決IE的內存泄漏方式_第5頁
已閱讀5頁,還剩8頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、Web開發的發展    在過去一些的時候,Web開發人員并沒有太多的去關注內存泄露問題。那時的頁面間聯系大都比較簡單,并主要使用不同的連接地址在同一個站點中導航,這樣的設計方式是非常有利于瀏覽器釋放資源的。即使Web頁面運行中真的出現了資源泄漏,那它的影響也是非常有限而且常常是不會被人在意的。    今天人們對Web應用有了高更的要求。一個頁面很可能數小時不會發生URL跳轉,并同時通過Web服務動態的更新頁面內容。復雜的事件關聯設計、基于對象的JScript和DHTML技術的廣泛采用,使得代碼的能力達到了其承受的極

2、限。在這樣的情況和改變下,弄清楚內存泄露方式變得非常的急迫,特別是過去這些問題都被傳統的頁面導航方法給屏蔽了。    還算好的事情是,當你明確了希望尋找什么時,內存泄露方式是比較容易被確定的。大多數你能遇到的泄露問題我們都已經知道,你只需要少量額外的工作就會給你帶來好處。雖然在一些頁面中少量的小泄漏問題仍會發生,但是主要的問題還是很容易解決的。泄露方式    在接下來的內容中,我們會討論內存泄露方式,并為每種方式給出示例。其中一個重要的示例是JScript中的Closure技術,另一個示例是在事件執行中使用Clos

3、ures。當你熟悉本示例后,你就能找出并修改你已有的大多數內存泄漏問題,但是其它Closure相關的問題可能又會被忽視。現在讓我們來看看這些個方式都有什么: 1、循環引用(Circular References) IE瀏覽器的COM組件產生的對象實例和網頁腳本引擎產生的對象實例相互引用,就會造成內存泄漏。這也是Web頁面中我們遇到的最常見和主要的泄漏方式; 2、內部函數引用(Closures) Closures可以看成是目前引起大量問題的循環應用的一種特殊形式。由于依賴指定的關鍵字和語法結構,Closures調用是比較容易被我們發現的; 3、頁面交叉泄漏(Cros

4、s-Page Leaks) 頁面交叉泄漏其實是一種較小的泄漏,它通常在你瀏覽過程中,由于內部對象薄計引起。下面我們會討論DOM插入順序的問題,在那個示例中你會發現只需要改動少量的代碼,我們就可以避免對象薄計對對象構建帶來的影響; 4、貌似泄漏(Pseudo-Leaks) 這個不是真正的意義上的泄漏,不過如果你不了解它,你可能會在你的可用內存資源變得越來越少的時候極度郁悶。為了演示這個問題,我們將通過重寫Script元素中的內容來引發大量內存的"泄漏"。循環引用    循環引用基本上是所有泄漏的始作俑者。通常情況下,腳本引擎通

5、過垃圾收集器(GC)來處理循環引用,但是某些未知因數可能會妨礙從其環境中釋放資源。對于IE來說,某些DOM對象實例的狀態是腳本無法得知的。下面是它們的基本原則:        Figure 1: 基本的循環引用模型    本模型中引起的泄漏問題基于COM的引用計數。腳本引擎對象會維持對DOM對象的引用,并在清理和釋放DOM對象指針前等待所有引用的移除。在我們的示例中,我們的腳本引擎對象上有兩個引用:腳本引擎作用域和DOM對象的expando屬性。當終止腳本引擎時第一個引用會釋放,DOM對象引用由

6、于在等待腳本擎的釋放而并不會被釋放。你可能會認為檢測并修復假設的這類問題會非常的容易,但事實上這樣基本的的示例只是冰山一角。你可能會在30個對象鏈的末尾發生循環引用,這樣的問題排查起來將會是一場噩夢。    如果你仍不清楚這種泄漏方式在HTML代碼里到底怎樣,你可以通過一個全局腳本變量和一個DOM對象來引發并展現它。<html>    <head>        <script language=

7、"JScript">        var myGlobalObject;        function SetupLeak()                    / First

8、 set up the script scope to element reference            myGlobalObject = document.getElementById("LeakedDiv");           

9、60;/ Next set up the element to script scope reference            document.getElementById("LeakedDiv").expandoProperty = myGlobalObject;      

10、60;         function BreakLeak()                    document.getElementById("LeakedDiv").expandoProperty = null;   

11、             </script>    </head>    <body onload="SetupLeak()" onunload="BreakLeak()">        <div 

12、;id="LeakedDiv"></div>    </body></html>    你可以使用直接賦null值得方式來破壞該泄漏情形。在頁面文檔卸載前賦null值,將會讓腳本引擎知道對象間的引用鏈沒有了。現在它將能正常的清理引用并釋放DOM對象。在這個示例中,作為Web開發員的你因該更多的了解了對象間的關系。    作為一個基本的情形,循環引用可能還有更多不同的復雜表現。對基于對象的JScript,一個通常用法

13、是通過封裝JScript對象來擴充DOM對象。在構建過程中,你常常會把DOM對象的引用放入JScript對象中,同時在DOM對象中也存放上對新近創建的JScript對象的引用。你的這種應用模式將非常便于兩個對象之間的相互訪問。這是一個非常直接的循環引用問題,但是由于使用不用的語法形式可能并不會讓你在意。要破環這種使用情景可能變得更加復雜,當然你同樣可以使用簡單的示例以便于清楚的討論。<html>    <head>        <script&

14、#160;language="JScript">        function Encapsulator(element)                    / Set up our element   &#

15、160;        this.elementReference = element;            / Make our circular reference            element.expan

16、doProperty = this;                function SetupLeak()                    / The leak happe

17、ns all at once            new Encapsulator(document.getElementById("LeakedDiv");                function BreakLeak() 

18、60;                  document.getElementById("LeakedDiv").expandoProperty = null;                </script&g

19、t;    </head>    <body onload="SetupLeak()" onunload="BreakLeak()">        <div id="LeakedDiv"></div>    </body></html>

20、    更復雜的辦法還有記錄所有需要解除引用的對象和屬性,然后在Web文檔卸載的時候統一清理,但大多數時候你可能會再造成額外的泄漏情形,而并沒有解決你的問題。    to be continued .    / Closure我沒有翻,他的表現是內部函數,并且可以訪問父函數的變量,有人翻成閉包被罵啦一頭包。這一節講Closures引起的內存泄漏,最后我還是決定把Closures翻譯成了閉包或閉包函數。而且又在KB中看到一個對Closures的解釋,它是這么說的:<HTML><H

21、EAD><script language="javascript">function initpage()    window.setTimeout("window.location.reload()", 500, "javascript");</script></HEAD><body onload="initpage()" ><div class

22、='menu' id='menu'></div><script language='javascript'>hookup(document.getElementById('menu');function hookup(element)    element.attachEvent( "onmouseover", mouse);    function&#

23、160;mouse ()         </script></body></HTML>    In this code, the handler (the mouse function) is nested inside the attacher (the hookup function). This arrangement means that the handler is closed over the scope of t

24、he caller (this arrangement is named a "closure"). 閉包函數(Closures)    由于閉包函數會使程序員在不知不覺中創建出循環引用,所以它對資源泄漏常常有著不可推卸的責任。而在閉包函數自己被釋放前,我們很難判斷父函數的參數以及它的局部變量是否能被釋放。實際上閉包函數的使用已經很普通,以致人們頻繁的遇到這類問題時我們卻束手無策。在詳細了解了閉包背后的問題和一些特殊的閉包泄漏示例后,我們將結合循環引用的圖示找到閉包的所在,并找出這些不受歡迎的引用來至何處。  &#

25、160;     Figure 2. 閉包函數引起的循環引用    普通的循環引用,是兩個不可探知的對象相互引用造成的,但是閉包卻不同。代替直接造成引用,閉包函數則取而代之從其父函數作用域中引入信息。通常,函數的局部變量和參數只能在該被調函數自身的生命周期里使用。當存在閉包函數后,這些變量和參數的引用會和閉包函數一起存在,但由于閉包函數可以超越其父函數的生命周期而存在,所以父函數中的局部變量和參數也仍然能被訪問。在下面的示例中,參數1將在函數調用終止時正常被釋放。當我們加入了一個閉包函數后,一個額外的引用產生,并且這個引用在

26、閉包函數釋放前都不會被釋放。如果你碰巧將閉包函數放入了事件之中,那么你不得不手動從那個事件中將其移出。如果你把閉包函數作為了一個expando屬性,那么你也需要通過置null將其清除。    同時閉包會在每次調用中創建,也就是說當你調用包含閉包的函數兩次,你將得到兩個獨立的閉包,而且每個閉包都分別擁有對參數的引用。由于這些顯而易見的因素,閉包確實非常用以帶來泄漏。下面的示例將展示使用閉包的主要泄漏因素:<html>    <head>     

27、;   <script language="JScript">        function AttachEvents(element)                    / This structure

28、0;causes element to ref ClickEventHandler            element.attachEvent("onclick", ClickEventHandler);            function ClickEventHandle

29、r()                            / This closure refs element             

30、0;              function SetupLeak()                    / The leak happens all at once 

31、60;          AttachEvents(document.getElementById("LeakedDiv");                function BreakLeak()         &#

32、160;              </script>    </head>    <body onload="SetupLeak()" onunload="BreakLeak()">        &l

33、t;div id="LeakedDiv"></div>    </body></html>    如果你對怎么避免這類泄漏感到疑惑,我將告訴你處理它并不像處理普通循環引用那么簡單。"閉包"被看作函數作用域中的一個臨時對象。一旦函數執行退出,你將失去對閉包本身的引用,那么你將怎樣去調用detachEvent方法來清除引用呢?在Scott Isaacs的MSN Spaces上有一種解決這個問題的有趣方法。這個方法使用一個額外的引

34、用(原文叫second closure,可是這個示例里致始致終只有一個closure)協助window對象執行onUnload事件,由于這個額外的引用和閉包的引用存在于同一個對象域中,于是我們可以借助它來釋放事件引用,從而完成引用移除。為了簡單起見我們將閉包的引用暫存在一個expando屬性中,下面的示例將向你演示釋放事件引用和清除expando屬性。<html>    <head>        <script language=&q

35、uot;JScript">        function AttachEvents(element)                    / In order to remove this we need to

36、 put            / it somewhere. Creates another ref            element.expandoClick = ClickEventHandler;      

37、      / This structure causes element to ref ClickEventHandler            element.attachEvent("onclick", element.expandoClick);     

38、60;      function ClickEventHandler()                            / This closure refs element  &

39、#160;                         function SetupLeak()                    

40、/ The leak happens all at once            AttachEvents(document.getElementById("LeakedDiv");                function

41、0;BreakLeak()                    document.getElementById("LeakedDiv").detachEvent("onclick",              

42、0; document.getElementById("LeakedDiv").expandoClick);            document.getElementById("LeakedDiv").expandoClick = null;             

43、0;  </script>    </head>    <body onload="SetupLeak()" onunload="BreakLeak()">        <div id="LeakedDiv"></div>    

44、;</body></html>    在這篇KB文章中,實際上建議我們除非迫不得已盡量不要創建使用閉包。文章中的示例,給我們演示了非閉包的事件引用方式,即把閉包函數放到頁面的全局作用域中。當閉包函數成為普通函數后,它將不再繼承其父函數的參數和局部變量,所以我們也就不用擔心基于閉包的循環引用了。在非必要的時候不使用閉包這樣的編程方式可以盡量使我們的代碼避免這樣的問題。    最后,腳本引擎開發組的Eric Lippert,給我們帶來了一篇關于閉包使用通俗易懂的好文章。他的最終建議也是希望在真正必要的時候

45、才使用閉包函數。雖然他的文章沒有提及閉包會使用的真正場景,但是這兒已有的大量示例非常有助于大家起步。    to be continued .不得不說Eric Lippert同志疾呼的:Don't use closures unless you really need closure semantics. In most cases, non-nested functions are the right way to go. 是非常消極的應付之辭。今天關于IE內存泄漏的文章已有很多,而且很大部分就是微軟的自己人在解釋,甚至象Eric Lippert這樣引擎

46、開發組的成員。但是他們的文章始終沒有正面承認其實這就是IE的bug,而且是非常嚴重的bug,這事情其實完全不關腳本引擎對象和DOM對象的事。就是微軟對產品不負責任的表現,不說IE4,1997那個春天那是太遙遠了點,但是IE6也是2001年隨xp發布的。使用COM可以給他們的開發帶來很多便利,當然也利用很多現成的東西,可是居然在帶來這樣的嚴重問題后,他們卻把大部分責任歸咎于不合理和不正確的使用Closures技術!對于循環引用產生Memory Leak我根本就是不好意思提了,那樣的問題是應該發生的嗎?那就讓我們拭目以待看IE7怎么解決這堆shit問題吧,當然我希望不要再看到類似:Do not u

47、se closures unless they are necessary. 這樣的扯淡建議!頁面交叉泄漏(Cross-Page Leaks)     這種基于插入順序而常常引起的泄漏問題,主要是由于對象創建過程中的臨時對象未能被及時清理和釋放造成的。它一般在動態創建頁面元素,并將其添加到頁面DOM中時發生。一個最簡單的示例場景是我們動態創建兩個對象,并創建一個子元素和父元素間的臨時域(譯者注:這里的域(Scope)應該是指管理元素之間層次結構關系的對象)。然后,當你將這兩個父子結構元素構成的的樹添加到頁面DOM樹中時,這兩個元素將會繼承頁面D

48、OM中的層次管理域對象,并泄漏之前創建的那個臨時域對象。下面的圖示示例了兩種動態創建并添加元素到頁面DOM中的方法。在第一種方法中,我們將每個子元素添加到它的直接父元素中,最后再將創建好的整棵子樹添加到頁面DOM中。當一些相關條件合適時,這種方法將會由于臨時對象問題引起泄漏。在第二種方法中,我們自頂向下創建動態元素,并使它們被創建后立即加入到頁面DOM結構中去。由于每個被加入的元素繼承了頁面DOM中的結構域對象,我們不需要創建任何的臨時域。這是避免潛在內存泄漏發生的好方法。        Figure 3. DOM插入順序泄漏模型&#

49、160;   接下來,我們將給出一個躲避了大多數泄漏檢測算法的泄漏示例。因為我們實際上沒有泄漏任何可見的元素,并且由于被泄漏的對象太小從而你可能根本不會注意這個問題。為了使我們的示例產生泄漏,在動態創建的元素結構中將不得不內聯的包含一個腳本函數指針。在我們設置好這些元素間的相互隸屬關系后這將會使我們泄漏內部臨時腳本對象。由于這個泄漏很小,我們不得不將示例執行成千上萬次。事實上,一個對象的泄漏只有很少的字節。在運行示例并將瀏覽器導航到一個空白頁面,你將會看到兩個版本代碼在內存使用上的區別。當我們使用第一種方法,將子元素加入其父元素再將構成的子樹加入頁面DOM,我們的

50、內存使用量會有微小的上升。這就是一個交叉導航泄漏,只有當我們重新啟動IE進程這些泄漏的內存才會被釋放。如果你使用第二種方法將父元素加入頁面DOM再將子元素加入其父元素中,同樣運行若干次后,你的內存使用量將不會再上升,這時你會發現你已經修復了交叉導航泄漏的問題。<html>    <head>        <script language="JScript">    &#

51、160;   function LeakMemory()                    var hostElement = document.getElementById("hostElement");        &#

52、160;   / Do it a lot, look at Task Manager for memory response            for(i = 0; i < 5000; i+)      

53、60;                     var parentDiv =                    document.createElement(&qu

54、ot;<div onClick='foo()'>");                var childDiv =                    document.

55、createElement("<div onClick='foo()'>");                / This will leak a temporary object            

56、;    parentDiv.appendChild(childDiv);                hostElement.appendChild(parentDiv);                hostElement.remo

57、veChild(parentDiv);                parentDiv.removeChild(childDiv);                parentDiv = null;     

58、           childDiv = null;                        hostElement = null;      

59、;          function CleanMemory()                    var hostElement = document.getElementById("hostElement"); 

60、0;          / Do it a lot, look at Task Manager for memory response            for(i = 0; i < 5000; i+

61、)                            var parentDiv =                 

62、0;  document.createElement("<div onClick='foo()'>");                var childDiv =               

63、;     document.createElement("<div onClick='foo()'>");                / Changing the order is important, this won't leak&#

64、160;               hostElement.appendChild(parentDiv);                parentDiv.appendChild(childDiv);       &

65、#160;        hostElement.removeChild(parentDiv);                parentDiv.removeChild(childDiv);              

66、  parentDiv = null;                childDiv = null;                       &

67、#160;hostElement = null;                </script>    </head>    <body>        <button onclick="Leak

68、Memory()">Memory Leaking Insert</button>        <button onclick="CleanMemory()">Clean Insert</button>        <div id="hostElement"></div>

69、    </body></html>    這類泄漏應該被澄清,因為這個解決方法有悖于我們在IE中的一些有益經驗。創建帶有腳本對象的DOM元素,以及它們已進行的相互關聯是了解這個泄漏的關鍵點。這實際上這對于泄漏來說是至關重要的,因為如果我們創建的DOM元素不包含任何的腳本對象,同時使用相同的方式將它們進行關聯,我們是不會有任何泄漏問題的。示例中給出的第二種技巧對于關聯大的子樹結構可能更有效(由于在那個示例中我們一共只有兩個元素,所以建立一個和頁面DOM不相關的樹結構并不會有什么效率問題)。第

70、二個技巧是在創建元素的開始不關聯任何的腳本對象,所以你可以安全的創建子樹。當你把你的子樹關聯到頁面DOM上后,再繼續處理你需要的腳本事件。牢記并遵守關于循環引用和閉包函數的使用規則,你不會再在掛接事件時在你的代碼中遇到不同的泄漏。    我真的要指出這個問題,因為我們可以看出不是所有的內存泄漏都是可以很容易發現的。它們可能都是些微不足道的問題,但往往需要成千上萬次的執行一個更小的泄漏場景才能使問題顯現出來,就像DOM元素插入順序引起的問題那樣。如果你覺得使用所謂的"最佳"經驗來編程,那么你就可以高枕無憂,但是這個示例讓我們看到,即使是

71、"最佳"經驗似乎也可能帶來泄漏。我們這里的解決方案希望能提高這些已有的好經驗,或者介紹一些新經驗使我們避免泄漏發生的可能。    to be continued .    不得不承認仔細的弄完這節的內容后,我有點被搞糊涂了。這個所謂的什么Cross-Page Leaks,是目前IE所有內存泄漏中我認為最嚴重的,前面兩節說道的循環引用和閉包函數都是我們可以發現并合理避免的。這里先不說Cross-Page Leaks到底有多少種場景,單示例給出的這個DOM插入順序問題就夠搞死人了。因為在使用Dhtml時,使用一個Get

72、Xxx、CreateXxx或GenerateXxx函數來得到一個復雜的Html對象結構,再attach到需要的primary DOM上是再平常不過的操作了,現在連這樣都要帶來Leak,真的是印證了這年頭連蘿卜都靠不住。之前看到過一個說法,由于IE的一些窗口之間有所有(owner)關系,比如誰是誰的openner啥的,所以IE會保持一些navigate away的頁面內容,以便于被別的頁面引用。可是不知道IE怎么搞得,連頁面跳轉到了別的domain或完全的空白(about:blank)頁中后,所占用的內存還是不能釋放。這樣嚴重的bug,微軟還能美其名曰Cross-Page Leaks,真的是絕倒

73、。其實只要能保證navigate to other domain或navigate to blank page后完全釋放內存(這里根本都不需要GC,直接全部release就行了),其它的泄漏問題也就沒有那么尖銳了,因為這時用戶在不關閉IE的情況下總還有個完全清理并回收內存的機會。那種運行了很久后,IE占用2,3百M內存(PM+VM)的情況就能大大的減小。不能回收內存的另一個莫名其妙的問題是,這時腳本的引擎的效率會變得異常的低。話又說回來,現在的機器,都是512M或1G的內存,IE就是占用2,3百M內存,甚至3,4百M也不是什么不能忍受的問題。可是這個時候,腳本引擎不知道怎么搞得,運行效率變得非常的低,似乎是每次GC腳本引擎都要把那些已leak的內存完全的sweep and mark一遍,那個代價可想而知。當然這是一個猜想,否則也無法解釋為什么腳本引擎的效率變得如此的低。貌似泄漏(Pseudo-Leaks)     在大多數時候,一些APIs的實際的行為和它們預期的行為可能會導致你錯誤的判斷內存泄漏。貌似泄漏大多數時候總是出現在同一個頁面的動態腳本操作中,而在從一個頁面跳轉到空白頁面的時候發生是非常少見的。那你怎么能象排除

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論