天天看點

Javascript的記憶體管理介紹記憶體的生命周期垃圾回收機制标記和掃描算法

原文位址

介紹

低級語言,像C語言具有能夠進行記憶體管理的工具,比如malloc( )和free( ),另一方面,當一個變量被建立時,javascript的記憶體會自動配置設定,當變量不再使用時,也會自動釋放。後面的這一過程被稱為垃圾回收機制。這種垃圾回收機制是混亂管理的緣由,這給js(或其他進階語言)開發者帶來他們能夠不去關心記憶體是如何管理的感覺。這是錯誤的。

記憶體的生命周期

不論哪種程式設計語言,記憶體的生命周期都是相同的:

  1.  Allocate the memory you need  配置設定你需要的記憶體
  2. Use the allocated memory (read, write)  使用配置設定的記憶體(讀和寫)
  3. Release the allocated memory when it is not needed anymore  當你不需要時釋放記憶體

第一二部分在是以語言都是顯式的,最後一部分是在低級語言中是顯式的,但是在進階語言中卻是隐式的,比如JavaScript。

javascript中的記憶體配置設定:

1、值得初始化,為了減輕程式員的程式設計負擔,js在配置設定的時候就聲明了值。

2、由函數傳回來配置設定。

使用變量:

使用變量基本上就是讀和寫配置設定的記憶體。我們讀寫變量的值或者是一個對象的屬性,甚至可以傳遞一個參數給函數來處理。

當不需要的時候釋放記憶體:

大多數記憶體管理的問題都出現在這個時候,這裡最難的就是找到不再需要這塊記憶體的那個時間。通常這都是由開發人員來确定程式中的哪塊記憶體不再需要,并釋放它。

進階語言都嵌入了垃圾回收機制,其工作原理是跟蹤記憶體配置設定和使用,以便找到何時不再需要一塊已配置設定的記憶體,在這種情況下,它将自動釋放。這個過程隻能是模糊的,因為程式一般不知道一塊記憶體是否還需要,這是不可判斷的(這不能由算法解決)。

垃圾回收機制

如上所述,普遍問題是自動去查找一塊配置設定的記憶體是否不再需要這是不可判斷的。垃圾回收機制實作了如何解決這個問題的限制。本節将幫助了解垃圾回收機制的原理以及局限性。

垃圾回收機制的算法依賴的是引用原理(the notion of reference.)。在記憶體管理的環境中,一個對象被看做是另一個對象的引用,如果前者對後者具有一個通路(顯式或者隐式)。比如,js對象具有對其原型(隐式)和屬性(顯式)的引用。

這裡所說的“對象”的概念擴充到比正常js對象更廣泛,并且包含函數作用域(以及全局詞法作用域)。

下面接受的是最原始的垃圾回收集算法。該算法将“一個對象不再被需要”定義為“一個對象沒有引用它的其他對象”。如果一個對象有0個引用指向,則該對象被認為是可回收的。

var o = { 
  a: {
    b:2
  }
}; 
// 2 objects are created. One is referenced by the other as one of its properties.
// The other is referenced by virtue of being assigned to the 'o' variable.
// Obviously, none can be garbage-collected




var o2 = o; // the 'o2' variable is the second thing that 
            // has a reference to the object
o = 1;      // now, the object that was originally in 'o' has a unique reference
            // embodied by the 'o2' variable


var oa = o2.a; // reference to 'a' property of the object.
               // This object has now 2 references: one as a property, 
               // the other as the 'oa' variable


o2 = "yo"; // The object that was originally in 'o' has now zero
           // references to it. It can be garbage-collected.
           // However what was its 'a' property is still referenced by 
           // the 'oa' variable, so it cannot be freed


oa = null; // what was the 'a' property of the object originally in o 
           // has zero references to it. It can be garbage collected.
           

限制:循環

當該算法涉及到循環時,就會受到限制,下面的事例中,建立了兩個對象并且互相引用,進而建構了一個循環。函數調用完畢後,它們講不會離開函數作用域,是以他們實際上是沒有作用的并且應該被釋放。然而引用計數算法認為每個對象都被引用了一次,是以沒有可以被垃圾回收的對象。

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


  return "azerty";
}


f();

           

真實的案例

在ie6、7中已知使用的是DOM對象的引用計數垃圾回收器。循環引用是一個常見的會産生記憶體洩漏的錯誤。

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

在上面的示例中,DOM元素“myDivElement”在“circularReference”屬性中具有對其自身的循環引用。如果該屬性未被明确删除或清空,引用計數的垃圾回收器将始終有一個完整引用,并且将DOM元素保留在記憶體中,即使它已從DOM樹中删除。如果DOM元素儲存大量資料(在上面的示例中使用“lotsOfData”屬性),則此資料消耗的記憶體永遠不會被釋放。

标記和掃描算法

該算法将“對象不再需要”定義歸納為“對象不可達”。

該算法假定知道一組被稱為roots的對象(在js中root就是全局對象)。垃圾回收器将定期從這些roots開始查找,然後找到所有從這些roots引用的對象,和所有不被他們引用的對象。從roots開始,垃圾回收器将找到的所有可到達得對象并回收不可到達的對象。

該算法比之前的算法更好,因為“對象零引用”所認為的該對象不可達,相反我們已經看到了循環就知道這不是真的。

截至2012年,所有現代浏覽器都裝有一個标記清除垃圾回收器。在過去幾年中,在JavaScript垃圾回收領域generational/incremental/concurrent/parallel garbage collection)中所做的所有改進都是該算法的實作改進,而不是垃圾回收算法本身的概念, 當“一個對象不再需要”。

循環不再是問題

在上面的第一個例子中,在函數調用傳回之後,2個對象不再被從全局對象可達的東西引用。是以,它們将被垃圾回收器發現不可達。

第二個例子也是一樣。一旦div和它的處理程式從根無法通路,它們都可以被垃圾回收,盡管引用彼此。

限制:對象需要明确無法通路

雖然這被标記為一個限制,這是一個很少在實踐中達到的,這就是為什麼沒有人經常關心垃圾回收的原因。

繼續閱讀