<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    JavaScript 內存泄露

    今天下午同事讓幫忙看web內存泄露問題。當時定位到創建ActiveX 對象的時候產生的,于是我對這個奇怪的問題進行了一些深入探索。 

    很多時候我都依賴javascript的垃圾回收機制,所以對以及C++ 操作內存語言常發生的內存泄露是很陌生的。當時創建回調函數用了閉包,當然最終的解決方法是也避免閉包調用。 

        

        隨著這個問題的浮出水面,我回憶起以前的一個項目中也應該存在這個內存泄露問題。于是查閱了相關資料把類似的問題總結下來,希望對大家也有幫助。

        原因:對于一門具有垃圾收回機制的語言存在內存泄露,其原因不外乎就是javascript腳本引擎存在bug

       很多時候,我們要做的不是去修正那樣的bug,而是想辦法去規避。

    目前發現的可能導致內存泄露的代碼有三種:

    · 循環引用

    · 自動類型裝箱轉換

    · 某些DOM操作

    下面具體的來說說內存是如何泄露的

    循環引用:這種方式存在于IE6FF2中(FF3未做測試),當出現了一個含有DOM對象的循環引用時,就會發生內存泄露。

    什么是循環引用?首先搞清楚什么是引用,一個對象A的屬性被賦值為另一個對象B時,則可以稱A引用了B。假如B也引用了A,那么AB之間構成了循環引用。同樣道理 如果能找到A引用B B引用C C又引用A這樣一組飲用關系,那么這三個對象構成了循環引用。當一個對象引用自己時,它自己形成了循環引用。注意,在js中變量永遠是對象的屬性,它可以指向對象,但決不是對象本身。

    循環引用很常見,而且通常是無害的,但如果循環引用中包含DOM對象或者ActiveX對象,那么就會發生內存泄露。例子:

    var a=document.createElement("div");
    var b=new Object();
    a.b=b;
    b.a=a; 

    很多情況下循環引用不是這樣的明顯,下面就是著名的閉包(closure)造成內存泄露的例子,每執行一次函數A()都會產生內存泄露。試試看,根據前面講的scope對象的知識,能不能找出循環引用?

    function A()...{
        var a=document.createElement("div");
        a.onclick=function()...{
            alert("hi");
        }
    }
    A(); 

    OK, 讓我們來看看。假設A()執行時創建的作用域對象叫做ScopeA 找到以下引用關系
    ScopeA引用DOM對象document.createElement("div");
    DOM對象document.createElement("div");引用函數function(){alert("hi")}
    函數function(){alert("hi")}引用ScopeA

    這樣就很清楚了,所謂closure泄露,只不過是幾個js特殊對象的循環引用而已。

    自動類型裝箱轉換:這種泄露存在于ie6 ie7中。這是極其匪夷所思的一個bug,看下面代碼

    var s="lalalalala";
    alert(s.length); 

    這段代碼怎么了?看看吧,"lalalalala"已經泄露了。關鍵問題出在s.length上,我們知道js的類型中,string并非對象,但可以對它使用.運算符,為什么呢?因為js的默認類型轉換機制,允許js在遇到.運算符時自動將string轉換為object型中對應的String對象。而這個轉換成的臨時對象100%會泄露(汗一下)。

    某些DOM操作也可能導致泄露 這些惡心的bug只存在于ie系列中。在ie7中 因為試圖fix循環引用bug而讓情況變得更糟,以至于我對寫這一段種滿了恐懼。

    ie6談起,下面是微軟的例子,

    <html>
        <head>
            <script language="JScript">...
            function LeakMemory()
            ...{
                var hostElement = document.getElementById("hostElement");
                // Do it a lot, look at Task Manager for memory response
                for(i = 0; i < 5000; i++)
                ...{
                    var parentDiv =
                        document.createElement("<div onClick='foo()'>");
                    var childDiv =
                        document.createElement("<div onClick='foo()'>");
                    // This will leak a temporary object
                    parentDiv.appendChild(childDiv);
                    hostElement.appendChild(parentDiv);
                    hostElement.removeChild(parentDiv);
                    parentDiv.removeChild(childDiv);
                    parentDiv = null;
                    childDiv = null;
                }
                hostElement = null;
            }

            function CleanMemory()
            ...{
                var hostElement = document.getElementById("hostElement");
                // Do it a lot, look at Task Manager for memory response
                for(i = 0; i < 5000; i++)
                ...{
                    var parentDiv =
                        document.createElement("<div onClick='foo()'>");
                    var childDiv =
                        document.createElement("<div onClick='foo()'>");
                    // Changing the order is important, this won't leak
                    hostElement.appendChild(parentDiv);
                    parentDiv.appendChild(childDiv);
                    hostElement.removeChild(parentDiv);
                    parentDiv.removeChild(childDiv);
                    parentDiv = null;
                    childDiv = null;
                }
                hostElement = null;
            }
            </script>
        </head>
        <body>
            <button onclick="LeakMemory()">Memory Leaking Insert</button>
            <button onclick="CleanMemory()">Clean Insert</button>
            <div id="hostElement"></div>
        </body>
    </html>

    看看結果吧,LeakMemory造成了內存泄露,而CleanMemory沒有,循環引用了么?仔細看看沒有。那么是什么問題呢?MS的解釋是"插入順序不對",必須先將父級元素appendChild。這聽起來有些模糊,這里給出一個比較恰當的等價描述:永遠不要使用DOM節點樹之外元素的appendChild方法

    我曾經看到過這樣的說法,創建dom的時候,先創建子節點,當子節點完善后一次性添加到頁面中,不要一點點朝頁面上加東西,盡量減少document刷新次數,這樣效率會高點。(打個比方就是應該像 LeakMemory )可見這里我還是被某些書籍誤導了。至少他沒有告訴我內存泄露的問題。

    接下來是ie7ie8 beta 1中運行這段程序,看到什么?沒看錯吧,2個都泄露了!別急,刷新一下頁面就好了。為什么呢?ie7改變了DOM元素的回收方式:在離開頁面時回收DOM樹上的所有元素,所以ie7下的內存管理非常簡單:在所有的頁面中只要掛在DOM樹上的元素,就不會泄露,沒掛在DOM樹上,肯定泄露。所以,ie7中記住一條原則:在離開頁面之前把所有創建的DOM元素掛到DOM樹上。

    接下來談談ie7的這個設計吧,坦白的說,這種做法純粹是偷懶的垃圾做法。動態垃圾回收不是保證所有內存都在離開頁面時收回,而是要保證內存的充分利用,運行時不回收,等到離開時回收有什么用?這只是名義上的避免泄露,其實是完全的泄露。況且還沒有回收DOM節點樹之外的元素。

     4.內存泄露的解決方案

    內存泄露怎么辦?真的以后不用閉包了么?沒法封裝控件了?這樣做還不如要了js程序員的命,嘿嘿。

    事實上,通過一些很簡單的小技巧,可以巧妙的繞開這些危險的bug

    to be continued......

    coming soon:

    · 顯式類型轉換

    · 避免事件導致的循環引用

    · 不影響返回值地打破循環引用

    · 延遲appendChild

    · 代理DOM對象

    · 顯式類型轉換

    首先說說最容易處理的情況 對于類型轉換造成的錯誤,我們可以通過顯式類型轉換來避免:

    var s=newString("lalalalala");//此處將string轉換成object
    alert(s.length); 

     這個太容易了,算不上正經方案。不過類型轉換泄露也就這一種處理方法了。

    · 避免事件導致的循環引用

    在比較成熟的js程序員里,把事件函數寫成閉包是再正常不過了:

    function A(){
        var a=document.createElement("div");
        a.onclick=function(){
            alert("hi");
        }

    這將導致內存泄露。按照IBM那兩位老大的說法,當然是把函數放外面或者a=null就沒問題了,不過還要訪問A()里面的變量呢?假如有下面的代碼:

    function A(){
        var a=document.createElement("div");
        var b=document.createElement("div");
        a.onclick=function(){
            alert(b.outerHTML);
        }
        return a;

     如何將它的邏輯表達出來 還避免內存泄露? 分析一下這個內存泄露的形式:只要onclick的外部環境中不包含a那么,就不會泄露。那么辦法有2個一是將環境到a的引用斷開 另一個是將function到環境的引用斷開,但是,如果要在函數中訪問b就不能將Function放到外面,如果要返回a的值,就不能a=null,怎么辦呢?

    解決方案1

    構造一個不含a的新環境

    function A(){
        var a=document.createElement("div");
        var b=document.createElement("div");
        a.onclick=BuildEvent(b);
        return a;
    }

    function BuildEvent(b)
    {
        return function(){
            alert(b.outerHTML);
        }

    a本身可以通過this訪問,將其它需要訪問的外層函數變量傳遞給BuildEvent就可以了。保持BuildEvent定義和調用的參數名一致,會帶來方便。

    解決方案2

    return 之后a=null,不可能? 看看下面:

    function A(){
        try{
            var a=document.createElement("div");
            var b=document.createElement("div");
            a.onclick= function(){
                alert(b.outerHTML);
            }
            return a;
        } finally {
            a=null;
        }

    finallytry之后執行,如果finall塊不返回值,才會返回try塊的返回值。

    · 延遲appendChild

    還記得函數的lazy initalize吧,對于ie惡心至極的DOM操作泄露,我們需要用類似的方法去處理。在一個函數中構造一個復雜對象,在需要的時候將之appendChildDOM樹上,這是很常見的做法,但在IE6中,這樣做將導致所謂的"插入順序內存泄露",沒有別的辦法,我們只能用一個數組parts保存子節點,編寫一個appendTo方法先序遍歷節點樹,去把它掛在某個DOM節點上。

    function appendTo(Element)
    ...{
        Element.appendChild(this);
        if(!this.parts)return;
        for(var i=0;i<this.parts.length;i++)
            parts.appendTo(this);

     

    · 垃圾箱

    對于ie7,我比較無可奈何,因為DOM對象不會被CG程序回收,只有離開頁面時會被回收,所以我的建議是:使用DOM要有節制,盡量多用innerHTML...... good luck.

    一旦你使用了DOM對象,千萬不要試圖o=null,你可以設置一個叫做Garbagediv并且將其display設置為none,將不用的DOM對象存入其中(就是appendChild上去)就好了

    · 代理對象

    這是Ext的做法,這里只是順帶提一下。將每個元素用一個"代理對象"操作,不論appendChild還是其他操作都不是對DOM對象本身的操作,而是通過這個代理對象操作。這是一個很不錯的Proxy模式,不過要想避免泄露還是需要一點功夫的,并非用了Proxy之后就不會泄露,有時反而更容易泄露。

    5 .FAQ

    內存泄露是內存占用很大么? 不是,即使1byte內存也叫做內存泄露。

    程序中提示,內存不足,是內存泄露么?不是,這一般是無限遞歸函數調用導致棧內存溢出。

    內存泄露是哪個區域泄露?堆區,棧區是不會泄露的。

    window對象是DOM對象么?不是,window對象參與的循環引用不會內存泄露。

    內存泄露后果是什么?大多數時候后果不很嚴重,但過多DOM操作會導致網頁執行變慢。

    跳轉頁面后,內存泄露仍然存在么?仍然存在,直到關閉瀏覽器

    FireFox也會內存泄露么?FF2仍然有內存泄露

    posted on 2009-10-27 01:52 -274°C 閱讀(6321) 評論(3)  編輯  收藏 所屬分類: web前端


    FeedBack:
    # re: [總結轉載]JavaScript 內存泄露[未登錄]
    2010-07-05 17:14 | Adrian
    我曾經看到過這樣的說法,創建dom的時候,先創建子節點,當子節點完善后一次性添加到頁面中,不要一點點朝頁面上加東西,盡量減少document刷新次數,這樣效率會高點。(打個比方就是應該像 LeakMemory )可見這里我還是被某些書籍誤導了。至少他沒有告訴我內存泄露的問題。

    --
    是這么說的,不過是需要一個承接對象document.createDocumentFragment去appendChild,最后把這個fragment再append到targetDOM。

    這樣寫好像不會leak。。。
    var targetDOM = ...,
    fragment = ...;
    for {
    fragment.appendChild(nodes)
    }
    targetDOM.append(fragment)  回復  更多評論
      
    # re: [總結轉載]JavaScript 內存泄露
    2010-07-07 11:22 | 路人
    @Adrian
    MS的解釋是"插入順序不對",必須先將父級元素appendChild。這聽起來有些模糊,這里給出一個比較恰當的等價描述:永遠不要使用DOM節點樹之外元素的appendChild方法。

    會不會leak 跟瀏覽器本身關系比較大。

      回復  更多評論
      
    # re: [總結轉載]JavaScript 內存泄露
    2012-12-03 18:29 | nerd
    受用了!謝謝  回復  更多評論
      

    常用鏈接

    留言簿(21)

    隨筆分類(265)

    隨筆檔案(242)

    相冊

    JAVA網站

    關注的Blog

    搜索

    •  

    積分與排名

    • 積分 - 914685
    • 排名 - 40

    最新評論

    主站蜘蛛池模板: a毛片免费全部在线播放**| 一级做a爰片性色毛片免费网站| CAOPORN国产精品免费视频| 免费乱理伦在线播放| 亚洲狠狠婷婷综合久久蜜芽| 国产日本一线在线观看免费 | 亚洲色图视频在线观看| 午夜视频在线免费观看| 亚洲国产精品久久久久婷婷老年| 国产一级淫片a免费播放口| 亚洲国产一区二区a毛片| 妻子5免费完整高清电视| 九九免费精品视频在这里| 国产亚洲精品精品国产亚洲综合| 久久精品成人免费观看97| 激情综合亚洲色婷婷五月| 精品国产免费观看一区| 美女羞羞视频免费网站| 久久久精品国产亚洲成人满18免费网站 | 爱情岛论坛网亚洲品质自拍| 免费无码av片在线观看| 亚洲日本一区二区三区| 免费a在线观看播放| 久久久高清免费视频| 18禁超污无遮挡无码免费网站| 亚洲变态另类一区二区三区| 亚洲AV成人一区二区三区AV| 99久久这里只精品国产免费 | 日日麻批免费40分钟无码| 亚洲国产精品白丝在线观看| 免费理论片51人人看电影| 色猫咪免费人成网站在线观看| 色爽黄1000部免费软件下载| 亚洲色偷偷综合亚洲av78| 国产成人精品久久亚洲| 免费黄网在线观看| 国产高清不卡免费在线| 免费一区二区无码视频在线播放| 亚洲AV无码国产精品色午友在线| 美女视频黄是免费的网址| 免费成人在线电影|