20170605-內存泄漏和垃圾回收

垃圾回收的必要性

因爲字符串、對象和數組沒有固定大小,因此當他們的大小已知時,才能對他們進行動態的存儲分配。JavaScript程序每次建立字符串、數組或對象時,解釋器都必須分配內存來存儲那個實體。只要像這樣動態地分配了內存,最終都要釋放這些內存以便他們可以被再用,不然,JavaScript的解釋器將會消耗完系統中全部可用的內存,形成系統崩潰。 ---《JavaScript權威指南(第四版)》javascript

JavaScript的解釋器能夠檢測到什麼時候程序再也不使用一個對象了,當他肯定了一個對象是無用的時候,他就知道再也不須要這個對象,能夠把它所佔用的內存釋放掉了。例如:java

var a = "before";
var b = "override a";
var a = b; //重寫a

這段代碼運行以後,「before」這個字符串失去了引用(以前是被a引用),系統檢測到這個事實以後,就會釋放該字符串的存儲空間以便這些空間能夠被再利用。node

垃圾回收原理

最常採用的垃圾回收有兩種方法:標記清除、引用計數git

標記清除

當執行流入到一個函數中時,會建立該函數的執行環境,執行環境中的變量都會被標記爲「進入環境」,從邏輯上講,永遠不能釋放「進入執行環境」變量所佔用的內存。由於只要執行流入相應的執行環境,就可能會用到這些變量。
垃圾收集器在運行的時候會給存儲在內存的中的變量都加上標記。而後,它會去掉環境中的變量以及被環境中的變量引用的變量的標記。而在此以後再被加上標記的變量將被視爲準備刪除的變量。最後,垃圾收集器完成內存清除工做,銷燬那些帶標記的值並回收它們所佔用的內存空間。github

引用計數

另外一種不太常見的垃圾回收策略是引用計數。引用計數的含義是跟蹤記錄每一個值被引用的次數。當聲明瞭一個變量並將一個引用類型賦值給該變量時,則這個值的引用次數就是1。相反,若是包含對這個值引用的變量又取得了另一個值,則這個值的引用次數就減1。當這個引用次數變成0時,則說明沒有辦法再訪問這個值了,於是就能夠將其所佔的內存空間給收回來。這樣,垃圾收集器下次再運行時,它就會釋放那些引用次數爲0的值所佔的內存。chrome

可是用這種方法存在問題:數組

function problem(){
    var objA = new Object()
    var objB = new Object()
    objA.someOtherObject = objB
    objB.someOtherObject = objA
}

在這個例子中,objA和objB經過各自的屬性相互引用;也就是說這兩個對象的引用次數都是2。在採用引用計數的策略中,因爲函數執行以後,這兩個對象都離開了做用域,函數執行完成以後,objA和objB還將會繼續存在,由於他們的引用次數永遠不會是0。這樣的相互引用若是說很大量的存在就會致使大量的內存泄露。緩存

引發內存泄漏的操做

用全局變量緩存數據

將全局變量做爲緩存數據的一種方式,將以後要用到的數據都掛載到全局變量上,用完以後也不手動釋放內存(由於全局變量引用的對象,垃圾回收機制不會自動回收),全局變量逐漸就積累了一些不用的對象,致使內存泄漏閉包

var x = [];
function grow() {
  x.push(new Array(1000000).join('x'));
  /*
     使用x數組進行某些操做
  */
  setTimeout(grow, 1000);
}
grow()

沒有清理的DOM元素引用

(function () {
  var nodes = '';
  var item = {
    // 爲了凸顯
    name: new Array(1000000).join('x')
  }
  nodes = document.getElementById("nodes")
  nodes.item = item
  nodes.parentElement.removeChild(nodes)
})()

notes變量指向的是頁面中的一個元素(也是內存中的一塊空間),當將 notes 對應的元素從頁面中移除後,其在內存中對應的空間因爲仍然由notes變量指向(引用),所以垃圾回收機制不會將這塊內存空間回收,從而致使內存泄漏ide

 被遺忘的定時器或者回調

var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

這樣的代碼很常見, 若是id爲Node的元素從DOM中移除, 該定時器仍會存在, 同時, 由於回調函數中包含對someResource的引用, 定時器外面的someResource也不會被釋放.

閉包循環引用

var theThing = null  
var replaceThing = function () {
  var originalThing = theThing
  var unused = function () {
    if (originalThing)
      console.log("hi")
  }
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log(someMessage)
    }
  };
};
setInterval(replaceThing, 1000)

這種內存泄漏的分析請參考here

參考

《JavaScript權威指南》
javascript典型內存泄漏及chrome的排查方法

相關文章
相關標籤/搜索