JS內存管理

JS有完善的內存處理機制,因此以前咱們不用特別的去關注這塊的實現。頁面不快了,刷新一下就行了;瀏覽器卡頓,重啓一下就OK。可是隨着SPA和移動APP的流行,以及將來可能存在的PWA的實現,JS內存可能成爲新的內存瓶頸。這也是寫本文的初衷。

1.什麼是內存泄漏

當咱們決定再也不使用某些內存時,因爲錯誤的編碼,未能使得GC(Gabbage Collection)正確的將這些內存回收的狀況,就是內存泄漏。javascript

2.內存的佔用,分配和回收

2.1 內存的佔用

圖片描述
一個對象佔用的內存分爲直接佔用內存(Shallow Size)和佔用總內存(Retained Size)。html

直接佔用內存:對象自己佔用的內存。典型的JavaScript對象都會有保留內存用來描述這個對象和存儲它的直接值。通常,只有數組和字符串會有明顯的直接佔用內存(Shallow Size)。但字符串和數組經常會在渲染器內存中存儲主要數據部分,僅僅在JavaScript對象棧中暴露一個很小的包裝對象。
佔用總內存:直接佔用內存和這個引用的依賴對象所佔用的內存。

賦值和New操做都會涉及到內存的佔用。java

2.2 內存的分配

Chrome V8的垃圾回收(GC)算法基於Generational Collection,內存被劃分爲兩種,分別稱爲Young Generation(YG)和Old Generation(OG)。node

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

由於在V8中,YG的GC過程會阻塞程序,而OG的GC不會阻塞。因此一般狀況下開發者更關心YG的細節。算法

YG又被平分爲兩部分空間,分別稱爲From和To。全部內存從To空間被分配出去,當To滿時,開始觸發GC,接下來細看一下。chrome

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

圖片描述

此時,一個程序須要爲D分配內存,但D須要的內存大小超出了To未分配的內存,以下圖。此時,觸發GC,頁面中止執行。
圖片描述瀏覽器

接着From和To進行對換,即原來的To空間被標誌爲From,From被標誌爲To。而且把活的變量值(例如B)標誌出來,而」垃圾「(例如AC)未被標誌,它們將會被清掉。
圖片描述閉包

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

圖片描述

至此,整個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對象。

圖片描述

從GC Root開始遍歷圖,全部能到達的節點稱爲活節點,若是存在GC Root不能到達的節點,那麼該節點稱爲「垃圾」,將會被回收,如圖中灰色的節點。

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

3. 致使內存泄漏的緣由

3.1 沒有徹底切斷與GC root之間的路徑

由於沒有徹底切斷與根節點之間的路徑,致使自動GC不會回收這部份內存,從而形成內存泄漏。

具體的緣由有:

  • 對象之間的相互引用
var a, b;
a.reference = b;
b.reference = a;
  • 錯誤使用了全局變量
a = "1234567";
至關於
window.a = "1234567";
  • DOM元素清空或刪除時,綁定的事件未清除
<div id="myDiv">
    <input type="button" value="Click me" id="myBtn">
</div>

<script type="text/javascript">
    var btn = document.getElementById('myBtn');
    btn.onclick = function () {
        document.getElementById('myDiv').innerHTML = 'Processing...';
        /* 清除事件綁定 */
        // btn.onclick = null;
    };
</script>
  • 閉包引用
function bindEvent() {
    var obj = document.getElementById('xxx');

    obj.onclick = function () {
        /** 空函數*/
    };

    /** delete this reference */
    // obj = null;
}
  • DOM元素清空或刪除時,子元素存在JS引用,致使子元素的全部父元素都不會被刪除
// b是a的子dom節點, a是body的子節點
var aElement = document.getElementById("a");
var bElement = document.getElementById("b");
document.body.removeChild(aElement);
// aElement = null;
// bElement = null;

3.2 過分佔用了內存空間

更多的出如今nodejs中,例如:

  • 無節制的循環
while(1) {
    // do sth
}
  • 過大的數組
var arr = [];
for (var i=0; i< 100000000000; i++) {
    var a = {
        'desc': 'an object'
    }
    arr.push(a);
}

總結

本文描述了內存分配和泄漏的基本原理,並說起了平常常遇到的集中的泄漏緣由。在下一篇文章中,將闡述如何肯定內存泄漏,以及可使用的工具和方法。

參考文獻:

相關文章
相關標籤/搜索