Javascript內存管理

什麼是內存

內存(Memory)也被稱爲內存儲器和主存儲器,其做用是用於暫時存放CPU中的運算數據,以及與硬盤等外部存儲器交換的數據。計算機中全部程序的運行都是在內存中進行的,所以內存的性能對計算機的影響很是大。javascript

內存生命週期

不管什麼語言,內存週期基本是一致的:java

  1. 分配你所須要的內存
  2. 使用分配到的內存(讀、寫)
  3. 不須要時將其釋放\歸還

JavaScript的內存分配

值的初始化算法

var n = 123; // 給數值變量分配內存
var s = "azerty"; // 給字符串分配內存

var o = {
   a: 1,
   b: null
}; // 給對象及其包含的值分配內存

// 給數組及其包含的值分配內存(就像對象同樣)
var a = [1, null, "abra"]; 

function f(a){
  return a + 2;
} // 給函數(可調用的對象)分配內存

 // 函數表達式也能分配一個對象
someElement.addEventListener('click', function(){
   someElement.style.backgroundColor = 'blue';
}, false);

 經過函數調用分配內存數組

var d = new Date(); // 分配一個 Date 對象
var e = document.createElement('div'); // 分配一個 DOM 元素

內存使用

使用值的過程其實是對分配內存進行讀取與寫入的操做。讀取與寫入多是寫入一個變量或者一個對象的屬性值,甚至傳遞函數的參數。瀏覽器

內存釋放

大多數內存管理的問題都在這個階段。在這裏最艱難的任務是找到「所分配的內存確實已經再也不須要了」。它每每要求開發人員來肯定在程序中哪一塊內存再也不須要而且釋放它。閉包

垃圾回收

引用計數

這是最簡單的垃圾收集器算法。若是沒有引用指向這個對象的時候,這個對象就被認爲是「能夠做爲垃圾收集」。函數

var o = { 
 a: {
   b:2
 }
}; 
// 兩個對象被建立,一個做爲另外一個的屬性被引用,另外一個被分配給變量o
// 很顯然,沒有一個能夠被垃圾收集


var o2 = o; // o2變量是第二個對「這個對象」的引用
o = 1;      // 如今,「這個對象」的原始引用o被o2替換了

var oa = o2.a; // 引用「這個對象」的a屬性
// 如今,「這個對象」有兩個引用了,一個是o2,一個是oa

o2 = "yo"; // 最初的對象如今已是零引用了
       // 他能夠被垃圾回收了
       // 然而它的屬性a的對象還在被oa引用,因此還不能回收

oa = null; // a屬性的那個對象如今也是零引用了
       // 它能夠被垃圾回收了

 循環引用的問題性能

當遇到循環的時候就會有一個限制。在下面的實例之中,建立兩個對象,而且互相引用,所以就會產生一個循環。當函數調用結束以後它們會走出做用域以外,所以它們就沒什麼用而且能夠被釋放。可是,基於引用計數的算法認爲這兩個對象都會被至少引用一次,因此它倆都不會被垃圾收集器收集。spa

function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o

  return "azerty";
}

f();

標記清除

JavaScript 中最經常使用的垃圾收集方式是標記清除(mark-and-sweep)。當變量進入環境(例如,在函數中聲明一個變量)時,就將這個變量標記爲「進入環境」。從邏輯上講,永遠不能釋放進入環境的變量所佔用的內存,由於只要執行流進入相應的環境,就可能會用到它們。而當變量離開環境時,則將其標記爲「離開環境」。這個算法由如下步驟組成:操作系統

  1. 這個垃圾收集器構建一個「roots」列表。Root是全局變量,被代碼中的引用所保存。在 JavaScript中,「window」就是這樣的做爲root的全局變量的例子。
  2. 全部的root都會被監測而且被標誌成活躍的(好比不是垃圾)。全部的子代也會遞歸地被監測。全部可以由root訪問的一切都不會被認爲是垃圾。
  3. 全部再也不被標誌成活躍的內存塊都被認爲是垃圾。這個收集器如今就能夠釋放這些內存並將它們返還給操做系統。 

內存泄漏

內存泄漏能夠被定義爲應用程序再也不須要的內存,可是因爲某些緣由不會返回到操做系統或可用內存池。

常見的JavaScript泄露

1. 全局變量

一個未聲明變量的引用會在全局對象內部產生一個新的變量。在瀏覽器的狀況,這個全局變量就會是window。

function foo(arg) {
   bar = "some text";
}
等同於:

function foo(arg) { window.bar = "some text"; }

若是bar被指望僅僅在foo函數做用域內保持對變量的引用,而且你忘記使用var去聲明它,一個意想不到的全局變量就產生了。

2.被遺忘的計時器和回調

setInterval 在 JavaScript 中是常常被使用的。大多數提供觀察者和其餘模式的回調函數庫都會在調用本身的實例變得沒法訪問以後對其任何引用也設置爲不可訪問。 可是在setInterval的狀況下,這樣的代碼很常見:




renderer所表明的對象在將來可能被移除,讓部分interval 處理器中代碼變得再也不被須要。然而,這個處理器不可以被收集由於interval依然活躍的(這個interval須要被中止從而表面這種狀況)。
若是這個interval處理器不可以被收集,那麼它的依賴也不可以被收集。這意味這存儲大量數據的severData也不可以被收集。

3. 閉包
閉包的特性是內部函數可以訪問外部函數的做用域。
var serverData = loadData(); setInterval(function() { var renderer = document.getElementById('renderer'); if(renderer) { renderer.innerHTML = JSON.stringify(serverData); } }, 5000); //每5000ms執行一次
var sayName = function(){
  var name = 'jozo';
  return function(){
    alert(name);
  }
};
var say = sayName(); 
say();

sayName返回了一個匿名函數,該函數又引用了sayName的局部變量name,sayName 調用後變量name應該被回收,可是因爲say繼續引用,致使沒法回收。

相關文章
相關標籤/搜索