垃圾回收gc --翻譯

原文在https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management.基本保持了平譯,並在一些地方作了概念解釋。(轉載請註明出處)javascript

介紹

高級語言,好比C,有底層的內存管理原語如malloc()和free()。可是對於Javascript來講,變量(string、object等等)被建立的時候分配內存,等變量再也不被使用的時候,內存被"自動"釋放。這個自動釋放的過程叫作"垃圾回收"。「自動」這個詞常令人困惑而且給了javascript(以及其餘的高級語言)開發者他們不須要關心內存管理的印象。這是錯誤的。java

內存生命週期

忽略具體的變成語言,內存的生命週期一般是這樣的:程序員

  1. 分配你須要的內存
  2. 使用他們(讀、寫操做)
  3. 當他們再也不須要的時候釋放他們

第二部分在全部的語言裏都是明顯的。第一和第三部分在低級語言裏是明顯的,可是在高級語言如JavaScript裏是很是隱祕的。算法

JavaScript的內存分配
變量的初始化

爲了避免讓程序的書寫者操心內存分配問題,JavaScript隨着變量的聲明自動分配了內存。數組

var n = 123; //爲1個數字分配了內存
var s = 'azerty';//爲一個字符串分配了內存

var o = {
  a:1,
  b:null
};//爲對象和裏邊的鍵值分配了內存

//(和對象同樣),爲數組和裏邊的元素分配了內存
var a = [1,null,'abra'];

function f(a){
  return a + 2;
}//爲一個函數分配了內存(函數是可調用對象)

//函數表達式也做爲1個對象被分配了內存
//譯者注:函數的表達式指的是下面的匿名函數的定義部分,JavaScript的函數能夠做爲函數的參數傳入,由於他是一種特殊的對象
someElement.addEventListener('click', function() {
  someElement.style.backgroundColor = 'blue';
}, false);
經過函數調用分配內存

一些函數調用的結果分配了內存瀏覽器

var d = new Date(); // 建立了一個Date對象,並分配了內存

var e = document.createElement('div'); // 建立了一個Dom元素,分配了內存

一些函數會分配新的變量或對象:併發

var s = 'azerty';
var s2 = s.substr(0, 3); // s2 是一個新的字符串
// 由於字符串是不可變對象, 
// JavaScript 可能不會再爲s2分配內存, 而僅僅讓s2記錄0-3這個區間

var a = ['ouais ouais', 'nan nan'];
var a2 = ['generation', 'nan nan'];
var a3 = a.concat(a2); 
// 新的數組s3有4個元素,鏈接了a和a2
使用內存

使用內存基本上就是讀寫已分配的內存。好比讀寫變量的值,對象的屬性,又或者給函數傳遞參數(譯者:這裏是說變量做爲參數傳遞到函數的行爲,就是使用這個變量內存的過程)。dom

當內存再也不須要的時候釋放

這個階段會產生許多內存管理的問題。最難的問題是找到何時「已分配的內存再也不須要」,這要求開發者去檢測什麼地方的內存再也不須要,而後釋放它們。函數

高級語言嵌入了一個叫作「垃圾回收」的功能,它的做用是追蹤內存分配,當發現一塊已分配的內存再也不須要的時候,自動釋放它。這是一個近似的過程,由於「判斷一段內存是否還須要保留」是不可斷定的(undecidable .can't be solved by an algorithm.這裏能夠理解爲,分配的內存還用不用只有程序員主觀的知道,若是他忘了,會致使內存泄漏,若是程序員手賤,釋放的早了,再訪問這塊內存就會致使內存訪問錯誤。語言的垃圾回收機制更像是一個第三者,雖然他很聰明,算法很強大,很能揣測程序意圖,可是畢竟不是本人)。code

引用

垃圾回收算法的主要理論依據是一個叫「引用」的概念。在內存管理的上下文中,若是一個對象有權利訪問另外一個對象(的內存,不管是顯式仍是隱式),會讓前者去引用後者。好比,Javascript對象有對他的原型隱式的引用,和對它屬性顯式的引用。

在內存管理的上下文中,「對象」的概念比一般的JavaScript的對象(Object)更爲寬泛,甚至包含函數做用域,或者全局的詞法做用域。

基於引用計數的垃圾回收

這是最簡單的垃圾回收算法。這種算法將「一個內存中對象再也不被須要」簡化爲"一個內存對象再也不被別的對象引用"。若是一個對象被引用的數量爲0,這個對象就被認爲應該被回收了。(譯者注:大多數此類算法都要求被管理的全部對象都有一個相似referenceCount的屬性,標識本身被引用了多少次)。

例子:
var o = {
  a:{
    b:2
  }
};
//2個對象被建立出來,1給被引用爲對象的屬性,而後外部的對象被變量o引用。顯然他們都不會被回收
var o2 = o;//變量o2又引用了o引用的對象
o = 1;//如今變量o再也不引用以前的對象,以前的對象只被o2引用了
var oa = o2.a;//如今做爲對象屬性的對象,又被變量oa引用了
o2 = 'yo';//如今最初被o引用的對象已經再被引用了,能夠被回收,可是它的a屬性還被oa引用,因此還不能被釋放
oa = null;//如今做爲a屬性的對象也再也不被引用了,最初被o引用的對象能夠被回收釋放了。
限制:循環引用

當引用出現循環,這種算法就會出現限制。下面的例子裏,2個對象被建立出來,而且彼此被另外一個引用,致使循環引用。函數執行完成後,他們會離開函數做用域,再也不起做用了,能夠被釋放了。可是,引用計數算法認爲他們2個都被引用了一次(函數接收後,變量o,o2會解掉對對象的引用),不能被回收。

function f() {
  var o = {};
  var o2 = {};
  o.a = o2; // o引用的對象的屬性a 引用 o2
  o2.a = o; // o2引用的對象的屬性a 引用 o

  return 'azerty';
}

f();
真實的例子

IE6和IE7使用引用計數管理DOM對象,因此循環引用是常見的致使內存泄漏的錯誤。

var div;
window.onload = function() {
  div = document.getElementById('myDivElement');
  div.circularReference = div;
  div.lotsOfData = new Array(10000).join('*');
};

這個例子裏,dom元素"myDivElement"被本身的circularReference屬性所引用(div.circularReference = div;)。若是這個屬性沒有明確的移除或者置爲null,即便myDivElement這個元素從DOM樹裏移除了,引用計數垃圾收集器還將老是擁有至少一個這個元素的引用,而且一直存在於內存中。若是DOM元素持有大量數據(好比這個例子裏的lotsOfData屬性),這個數據消耗的內存將不會被釋放回收。

基於標記-清除的垃圾回收

這個算法將"一個內存中對象再也不被須要"簡化爲"一個內存對象不可達"。(譯者注:原文是an object is unreachable。內存中的對象,若是程序沒法經過一些線索,如引用關係找到它,它就沒有存在的意義,是不可達的)。這種算法假定知道一組叫作根的對象,週期性的從這些「根」開始,查找到全部被根引用的對象,而後從這些對象開始,遞歸的查找下去。所以,從根開始查找的過程當中,垃圾收集器將會找到全部可達的內存對象和不可達的內存對象。

這個算法比上一個要好,由於「一個被0引用」的內存對象能夠推導出這個內存對象不可達,相反,出現循環引用的時候,反推卻不成立。

截止2012年,全部的現代瀏覽器都搭載了標記-清除垃圾回收器。過去幾年裏,JavaScript 的垃圾回收領域(分代/增量/併發/並行垃圾回收)技術的更新實現了這種算法的進步,可是對垃圾回收算法自己:何時"一個內存對象再也不須要"這個概念的處理,並無多大進步。(原文:but not improvements over the garbage collection algorithm itself nor its reduction of the definition of when "an object is not needed anymore).

循環引用再也不是問題

第一個例子裏,函數結束後,2個對象再也不被由全局對象可達的對象所引用,所以,他們被垃圾回收器認爲是不可達的。

限制:內存對象須要被顯式的設置爲不可達

即便這被標註爲一個限制,可是在實踐中不多遇到,這也是爲何不多有人會常常關注垃圾收集的緣由(說的是,在當前標記清除的技術下,程序員們再也不關注垃圾回收這個問題了)。

相關文章
相關標籤/搜索