JavaScript中的內存釋放


C、C++語言需要手動管理內存的分配與釋放(常用方法:malloc(), calloc(), realloc()和free()等)。而JavaScript與Java、C#相似,內置了垃圾回收器,能自動管理內存的分配與釋放。

內存生命周期:

  1. 分配內存
  2. 使用分配的內存(讀與寫操作)
  3. 當應用程序不再需要時,釋放掉已分配的內存

lifecycle

雖然垃圾回收器能能自動管理內存分配、釋放,但並不意味着開發者不再需要關注內存管理。因為一些不好的編碼會導致內存泄露,即應用程序不再需要的內存沒有被釋放掉。因此了解內存管理是很重要的。

Javascript中的內存分配

當聲明變量時,JavaScript會自動為變量分配內存

var numberVar = 100; //為整數分配內存
var stringVar = 'node simplified';  // 為字符串分配內存 
var objectVar = {a: 1}; // 為對象分配內存
var a = [1, null, 'abra']; // 為數組分配內存
function f(a) {
  return a + 2;
} // 為函數分配內存 

GC(Garbage collection)

垃圾回收是追蹤並釋放應用程序不再使用的內存過程。垃圾回收器通過算法來實現追蹤應用程序不再使用的內存。主要涉及的垃圾回收算法如下:

  • Reference-counting garbage collection(引用計數)
  • Mark-and-sweep algorithm(標記清除)

Reference-counting garbage collection(引用計數)

引用計數算法是一種最基礎的垃圾回收算法,當一個對象的引用數為零時,會被自動回收。該算法將一個對象的引用數為0時視為應用程序不再需要的內存。

!function (){
  var o1 = {a: {b: 2}},// 兩個對象被創建。假如分別用A:{a: {b: 2}},B:{b: 2}表示,對象B被對象A的屬性a引用,對象A被賦值給變量o1。A和B的引用數都為1,因此不能被回收。
      o2 = o1; // 將對象A賦給變量o2。此時A引用數為2,B引用數1。
      o1 = 1;// 將變量o1對對象A引用切斷。此時A引用數為1,B引用數1。
  var oa = o2.a; // 將對象B賦值給變量oa。此時A引用數為1,B引用數2。
      o2 = 'foo'; // 將變量o2對對象A引用切斷。此時A引用數為0,B引用數1。因為對象A的a屬性被變量oa引用,因此對象A不能被釋放。
      oa = null; // 將變量oa對對象B引用切斷。此時A引用數為0,B引用數0。A與B會被回收。
}()

引用計數的限制:循環引用

循環引用存在一個限制。如下實例,兩個對象相互引用,形成一個循環引用。正常情況下,當函數執行完后,對應的內存會被釋放掉。而引用計數算法會將循環引用對象的引用數都視為至少為1,因此不能被回收。

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

  return 'azerty';
}

f();

cycle

常見問題

IE6-7的DOM對象是基於計數引用算法進行垃圾回收的。而循環引用通常會導致內存泄露:

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

如上述實例,DOM元素div通過自身的“circularReference”屬性循環引用自己。如果沒有顯式將該屬性刪除或設為null,計數引用垃圾回收器會始終持有至少一個引用。即使DOM元素從DOM樹種移除,DOM元素的內存會一直存在。如果DOM元素持有一些數據(如實例中“lotsData”屬性),該數據對應的內存也無法被釋放。了解更多參考--->IE<8循環引用導致的內存泄露

Mark-and-sweep algorithm(標記清除)

該算法將“對象不再需要”的定義簡化為“對象不可到達”。 這個算法假設有一組被稱為roots的對象(在JavaScript中,root就是全局對象)。垃圾回收器會定期地從這些roots開始,查找所有從根開始引用的對象,然后再查找這些對象引用的對象……。從roots開始,垃圾回收器會查找所有可到達對象,並回收不可到達的對象。

為了確定對象是否需要,該算法要確定對象是否可到達。由如下步驟組成:

  1. 垃圾回收器會創建一組roots,roots通常是持有引用的全局變量。在JavaScript中,window對象就可作為root的全局變量。
  2. 垃圾回收器會檢查所有的roots並標記為活躍狀態。然后遞歸遍歷所有的子變量。只要從root不能到達的都被標記為垃圾。
  3. 所有沒有被標記為活躍狀態的內存塊都被視為垃圾。垃圾回收器就可以釋放這部分內存並把釋放的內存返回給操作系統。

這個算法比引用計數算法更優,因為對於引用計數算法“零引用的對象”總是不可到達的,但反之則不一定,如循環引用。而標記清除算法不存在循環引用的問題。

截至2012年,所有現代瀏覽器都內置了標記清除垃圾回收器。在過去幾年里所有對JavaScript垃圾回收算法的改進(generational/incremental/concurrent/parallel garbage collection)都是基於標記清除算法來實現的,但並沒有改變標記清除算法本身和它對“對象不再需要”定義的簡化。

循環引用不再是問題

前面循環引用的實例中,在函數執行完后,兩個對象不再被全局對象可訪問的對象引用。因此這兩個對象被垃圾回收器標記為不可到達,接着被回收掉。 

限制:需要明確無法到達的對象

對於這個限制,實踐中很少遇見,所以開發者不太會去關心垃圾回收機制。

參考文章:

 

原文地址: https://github.com/cucygh/js-leakage-patterns/blob/master/JavaScript%E5%86%85%E5%AD%98%E9%82%A3%E7%82%B9%E4%BA%8B/JavaScript%E5%86%85%E5%AD%98%E9%82%A3%E7%82%B9%E4%BA%8B.md


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM