翻譯:javascript 內存管理

原文連接:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management?redirectlocale=en-US&redirectslug=JavaScript%2FMemory_Managementjavascript

javascript 內存管理 

介紹

低級語言(c語言),擁有底層的內存管理原語:malloc() 和 free()。而在 javascript 中,變量的內存是在建立過程當中自動分配的,當變量再也不被引用時會自動釋放其內存。後面的這一處理過程被稱爲「垃圾回收」。這裏的「自動」致使了一系列的困惑,而且給 javascript (高級語言) 的開發者帶來一種錯誤的印象,即他們不須要關心內存的管理。

內存的生命週期

拋開編程語言來講,幾乎全部的內存生命週期都具備相同的特色:
1. 按需分配內存
2. 讀,寫
3. 再也不被引用時,釋放
前兩個特色在全部的語言中都是顯而易見的,第三點在低級編程語言中也很容易可以辦到,可是在大部分高級語言中(好比 javascript)則很複雜。

javascript 中內存的分配

  • 變量的初始化

爲了方便開發者不去關心內存分配的事情,javascript 在變量申明的時候就已經順便分配了內存。java

var n = 123; // 給一個number分配內存
var s = "azerty"; // 給一個string分配內存
var o = {
  a: 1,
  b: null
}; // 給一個object分配內存

var a = [1, null, "abra"]; // (和object同樣)給一個array及裏面的值分配內存

function f(a){
  return a + 2;
} // 給一個function分配內存(function也能夠看成一個object)

// 函數的表達式一樣也是至關於給一個object分配內存
someElement.addEventListener('click', function(){
  someElement.style.backgroundColor = 'blue';
}, false);
  • 經過函數調用分配內存
一些函數調用會於給一個對象分配內存
var d = new Date();
var e = document.createElement('div');// 給一個dom元素分配內存
一些方法會致使給一個新的變量或對象分配內存
var s = "azerty";
var s2 = s.substr(0, 3); // s2 是一個新的string
// 因爲strings是一些不可變的值組成的,javascript可能不會給其分配內存,而只是存儲一個[0,3]的範圍

var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2); // 新的array,包含四個元素,分別由a和a2的元素組成
  • 變量的使用
使用一個變量基本上也就意味着內存的讀和寫,這個過程能夠經過讀或寫變量的值或一個對象的屬性值或者甚至是給函數傳遞參數完成。
  • 內存再也不被引用時,釋放
內存管理中遇到的大多數問題都會集中在這個階段。這裏最難的任務的如何判斷一個已分配的內存再也不繼續被使用。一般這個問題是由開發者決定這段內存將再也不被使用,而後釋放它。
通常高級語言的編譯器會內嵌一小段程序,它被稱做「垃圾回收者",這個垃圾回收者的工做顧名思義,固然是回收垃圾的嘛,這裏的"垃圾"指的就是將再也不被使用的內存片斷。這個過程也只是個近似的處理,由於目前爲止,判斷某個片斷的內存是否正在被引用這個問題是不可斷定的,也就是說不能在多項式時間內求解。
 

垃圾回收

上面已經提到判斷一段內存是否再也不被引用是圖靈不可斷定問題。所以垃圾回收機制也就採用了一個限制性的方法來解決這個問題。這部分先會介紹一些必要的概念以便理解垃圾回收的算法及其限制的地方。
  • 引用

垃圾回收算法的主要思想主要依賴的一個概念就是引用。在一段內存管理的上下文中,若是一個對象訪問了另外一個對象(不管是顯式的仍是隱式的),咱們就說前者引用了後者。例如,一個 javascript 對象能夠訪問它的 prototype 屬性(隱式的), 也能夠調用它本身的屬性值(顯式的)。
在這個上下文中,"object" 的概念被擴展了,它指代的是不只是傳統的 object 對象,同時還包含了函數域(或全局詞法做用域)。
  • 基於引用計數的垃圾回收

這是最基本的垃圾回收算法。這個算法把問題"一個對象再也不被引用"簡化成"一個對象沒有被其餘對象引用"。若是一個對象上的引用計數爲0,則代表這個對象是能夠被看成垃圾回收的。
 
示例:
var o = { 
  a: {
    b:2
  }
}; // 建立了兩個對象,一個引用了另一個對象做爲其屬性 顯然,都不能夠被回收


var o2 = o; // o2 也引用了 o
o = 1; // 如今,o中原始的對象有一個惟一的引用 o2的變量中 

var oa = o2.a; // 引用o2的a屬性
// 這個對象產生了兩個引用,一個是o2的a對象,另外一個一個是oa變量

o2 = "yo"; // 原始的o對象如今是0引用
// o能夠被回收
// 然而其屬性a仍然被oa引用了,所以其內存不能被釋放

oa = null; // 原始對象o中的a是0引用了
// 此時能夠被回收
 
缺點:循環引用
這個基本算法有一個缺點,一個對象A引用了另外一對象B,對象B又反過來引用了A。這樣A,B之間就造成了一個循環。A,B可能都不會再引用,可是根據上面的算法A和B均不會被回收。
function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用了 o2
  o2.a = o; // o2 引用了 o

  return "azerty";
}

f();
 
現實生活中的例子
IE的6,7中針對 dom 對象使用的就是基於引用計數的垃圾回收機制。因此,在IE6,7中,很容易產生系統的內存泄露。
var div = document.createElement("div");
div.onclick = function(){
  doSomething();
}; // div 經過 事件引用了其onclick屬性
// handler一樣引用了div
// 循環引用,內存泄露
  • 標記-刪選 算法

這個算法將問題"一個對象再也不被引用"簡化成"一個對象是沒法訪問的"。
這個算法假設知道一組對象的根(在 javascript 中這個根就是全局變量 window),這個垃圾回收者會不間斷的自根節點遍歷其全部子節點。這樣,垃圾回收者最後會找出全部可訪問的對象及全部不可訪問的對象。
這個算法好於以前的算法,由於「一個引用計數爲0的對象」也是一個不可訪問的對象。而反過來卻不成立,好比循環引用。
截至2012年,全部現代瀏覽器都內置了一個標記和回收垃圾的收集器。在過去的幾年中,javascript的垃圾回收領域(代/增量/併發/並行垃圾收集)中的全部改進都是在實現這種算法的改進而不是改善垃圾收集算法自己也不是在改進問題描述的簡化模型上。
 
循環引用再也不是一個問題
上面第一個示例中,函數調用以後,這兩個對象將再也不被引用,其餘的變量直接可從全局變量中訪問。所以, 它們將被垃圾回收者認爲是不可訪問的。
第二個示例也是如此,一旦這個 div 及其 handler不可被頂級 roots 訪問,它們都將被回收掉,即便它們之間會產生互相引用。
 
缺點:對象須要顯式的指明不可訪問
雖然這被標記爲一個缺點,其實這在現實中很難出現,由於也不會有不少人常常關注垃圾的回收。

更多

相關文章
相關標籤/搜索