首頁 >web前端 >js教程 >JS記憶體管理實例講解

JS記憶體管理實例講解

小云云
小云云原創
2018-02-22 13:46:091718瀏覽

JS有完善的記憶體處理機制,所以之前我們不用特別的去關注這塊的實作。頁面不快了,刷新一下就好了;瀏覽器卡頓,重啟一下就OK。但是隨著SPA和行動APP的流行,以及未來可能存在的PWA的實現,JS記憶體可能成為新的記憶體瓶頸。

1.什麼是記憶體洩漏

當我們決定不再使用某些記憶體時,由於錯誤的編碼,未能使得GC (Gabbage Collection)正確的將這些記憶體回收的情況,就是記憶體洩漏。

2.記憶體的佔用,分配與回收

2.1 記憶體的佔用

JS記憶體管理實例講解
一個物件佔用的記憶體分為直接佔用記憶體(Shallow Size)和占用總記憶體(Retained Size)。

直接佔用記憶體:物件本身所佔用的記憶體。典型的JavaScript物件都會有保留記憶體用來描述這個物件和儲存它的直接值。一般,只有陣列和字串會有明顯的直接佔用記憶體(Shallow Size)。但字串和陣列常常會在渲染器記憶體中儲存主要資料部分,僅在JavaScript物件堆疊中暴露一個很小的包裝物件。
佔用總記憶體:直接佔用記憶體和這個引用的依賴物件所佔用的記憶體。

賦值和New運算都會牽涉到記憶體的佔用。

2.2 記憶體的分配

Chrome V8的垃圾回收(GC)演算法是基於Generational Collection,記憶體被分割為兩種,分別稱為為Young Generation(YG)和Old Generation(OG)。

所謂Young和Old是根據他們所佔用的時間來劃分的。內部存在YG的分配和回收快而頻繁,一般存在的時間很短,所以稱為Young;而在OG中則慢而少發生,所以稱為Old。

因為在V8中,YG的GC過程會阻塞程序,而OG的GC不會阻塞。所以通常開發者會比較關心YG的細節。

YG又平分為兩部分空間,分別稱為From和To。所有記憶體從To空間被分配出去,當To滿時,開始觸發GC,接下來細看一下。

某一時刻,To已經分A、B和C分配了內存,當前它剩下一小塊內存未分配出去,而From所有的內存都空閒著。

JS記憶體管理實例講解

此時,一個程式需要為D分配內存,但D所需的記憶體大小超出了To未分配的內存,如下圖。此時,觸發GC,頁面停止執行。
JS記憶體管理實例講解

接著From和To進行對換,即原來的To空間被標誌為From,From被標誌為To。並且把活的變數值(例如B)標誌出來,而」垃圾「(例如AC)未被標誌,它們將會被清除。
JS記憶體管理實例講解

活的B會被複製到To空間,而「垃圾」AC則被回收,同時,D被分配到To空間,最後成下圖的分佈

JS記憶體管理實例講解

至此,整個GC完成,此過程中頁面停止執行,所以要盡可能的快。當YG中的值存活比較久時,它會被推向OG,OG的空間滿時,觸發OG內的GC,OG的GC時會觸發YG的GC。

  • 每次分配都會使To的可用空間減小,程式又更接近GC

  • #YG的GC會阻塞程序,所以GC時間不宜太長10ms以內,因為16ms就會出現丟幀;GC不宜太頻繁

  • #某個值變成垃圾後,不會立刻釋放內存,只有在GC的時候所佔內存才會被回收。

2.2 內容皆來自參考文獻

2.3 記憶體的回收

GC Root是記憶體的根結節,在瀏覽器中它是window,在NodeJS中則是global物件。

JS記憶體管理實例講解

從GC Root開始遍歷圖,所有能到達的節點稱為活節點,如果存在GC Root無法到達的節點,那麼該節點稱為“垃圾”,將會被回收,如圖中灰色的節點。

至於根節點的回收,不受使用者的控制。

3. 導致記憶體洩漏的原因

#3.1 沒有完全切斷與GC root之間的路徑

因為沒有完全切斷與根節點之間的路徑,導致自動GC不會回收這部分內存,從而造成內存洩漏。

具體的原因有:

  • #物件之間的相互引用

  • ##

    <span style="font-size: 14px;">var a, b;<br>a.reference = b;<br>b.reference = a;<br></span>
  • 錯誤使用了全域變數
  • #

    <span style="font-size: 14px;">a = "1234567";<br>相当于<br>window.a = "1234567";<br></span>
    ##DOM元素清空或刪除時,綁定的事件未清除

<span style="font-size: 14px;"><p id="myp"><br>    <input type="button" value="Click me" id="myBtn"><br></p><br><br><script type="text/javascript"><br>    var btn = document.getElementById('myBtn');<br>    btn.onclick = function () {<br>        document.getElementById('myp').innerHTML = 'Processing...';<br>        /* 清除事件绑定 */<br>        // btn.onclick = null;<br>    };<br></script><br></span>

#閉包引用

    ##
<span style="font-size: 14px;">function bindEvent() {<br>    var obj = document.getElementById('xxx');<br><br>    obj.onclick = function () {<br>        /** 空函数*/<br>    };<br><br>    /** delete this reference */<br>    // obj = null;<br>}<br></span>
  • DOM元素清空或刪除時,子元素存在JS引用,導致子元素的所有父元素都不會被刪除

    • <span style="font-size: 14px;">// b是a的子dom节点, a是body的子节点<br>var aElement = document.getElementById("a");<br>var bElement = document.getElementById("b");<br>document.body.removeChild(aElement);<br>// aElement = null;<br>// bElement = null;<br></span>
    • 3.2 過度佔用了記憶體空間

    • 更多的出現在nodejs中,例如:


      ##無節制的迴圈

      <span style="font-size: 14px;">while(1) {<br>    // do sth<br>}<br></span>

      #過大的陣列

      ##
      <span style="font-size: 14px;">var arr = [];<br>for (var i=0; i< 100000000000; i++) {<br>    var a = {<br>        'desc': 'an object'<br>    }<br>    arr.push(a);<br>}<br></span>
      ## #相關推薦:############詳細介紹Linux中的記憶體管理#############php記憶體管理之垃圾回收機制的詳解(圖)### #########如何避免JavaScript的記憶體外洩及記憶體管理技巧######

      以上是JS記憶體管理實例講解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

      陳述:
      本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn