js垃圾回收


內存生命周期

  • 分配你所需要的內存
  • 使用分配到的內存(讀、寫)
  • 不需要時將其釋放\歸還

所有語言第二部分都是明確的.第一和第三部分在底層語言中是明確的,但在像 JavaScript 這些高級語言中,嵌入了'垃圾回收器',根據 Wiki 的定義,垃圾回收是一種自動的內存管理機制,用來追蹤不用的內存並自動釋放.

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 s = "azerty";
var s2 = s.substr(0, 3); // s2 是一個新的字符串
// 因為字符串是不變量,
// JavaScript 可能決定不分配內存,
// 只是存儲了 [0-3] 的范圍.

var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2);
// 新數組有四個元素,是 a 連接 a2 的結果

使用值

使用值的過程實際上是對分配內存進行讀取與寫入的操作.讀取與寫入可能是寫入一個變量或者一個對象的屬性值,甚至傳遞函數的參數.

垃圾回收

程序是運行在內存里的,當聲明一個變量、定義一個函數時都會占用內存.內存的容量是有限的,如果變量、函數等只有產生沒有消亡的過程,那遲早內存有被完全占用的時候.這個時候,不僅自己的程序無法正常運行,連其他程序也會受到影響.所以,在計算機中,我們需要垃圾回收.需要注意的是,定義中的“自動”的意思是語言可以幫助我們回收內存垃圾,但並不代表我們不用關心內存管理,如果操作失當,JavaScript 中依舊會出現內存溢出的情況.

當一些變量不在需要的時候,javascript 會對該這些變量占用的內存進行釋放,而'哪些被分配的內存確實已經不再需要了'是一個難實現的任務.在基礎語言中,往往要求開發人員來確定在程序中那一塊內存不在需要它之后釋放它.而高級語言解釋器中嵌入了'垃圾回收器',它的主要工作是跟蹤內存的分配和使用,以便當分配的內存不再使用時,自動釋放它.垃圾回收實現只能有限制的解決一般問題,因為要知道是否仍然需要某塊內存是無法判定的(無法通過某種算法解決).

引用

垃圾回收算法主要依賴於引用的概念.在內存管理的環境中,一個對象如果有訪問另一個對象的權限(隱式或者顯式),叫做一個對象引用另一個對象.例如,一個 Javascript 對象具有對它原型的引用(隱式引用)和對它屬性的引用(顯式引用).

在這里,“對象”的概念不僅特指 JavaScript 對象,還包括函數作用域(或者全局詞法作用域).

變量

在 js 中有全局變量個局部變量(這里的全局變量是 global object,而 client 中 js 的全局對象指的是 window,也就是當前瀏覽器窗口).

全局變量在一個瀏覽器頁面中始終是一只存在的.局部變量是在創建它的函數作用域中存在,當離開當前作用域的時候,會自動解除引用並進行釋放.

引用計數垃圾收集

這是最初級的垃圾收集算法.此算法把“對象是否不再需要”簡化定義為“對象有沒有其他對象引用到它”.如果沒有引用指向該對象(零引用),對象將被垃圾回收機制回收.

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屬性的那個對象現在也是零引用了
           // 它可以被垃圾回收了

限制:循環引用

該算法有個限制:無法處理循環引用的事例.在下面的例子中,兩個對象被創建,並互相引用,形成了一個循環.它們被調用之后會離開函數作用域,所以它們已經沒有用了,可以被回收了.然而,引用計數算法考慮到它們互相都有至少一次引用,所以它們不會被回收.

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

  return "azerty";
}

f();

標記清除算法

這個算法把“對象是否不再需要”簡化定義為“對象是否可以獲得”.

標記清除算法分為標記和清除兩個階段.這個算法假定設置一個叫做根(root)的對象(在 Javascript 里,根是全局對象).垃圾回收器將定期從根開始,找所有從根開始引用的對象,然后找這些對象引用的對象,然后對這些對象進行標記……從根開始,垃圾回收器將找到所有可以獲得的對象和收集所有不能獲得的對象.之后進入了清除階段,標記的對象會與內存中的對象進行比較,然后清除內存中那些沒有標記的對象

  • 標記階段,它將遍歷堆中所有對象,並對存活的對象進行標記;
  • 清除階段,對未標記對象的空間進行回收.

這是 JavaScript 中最常見的垃圾回收方式.從 2012 年起,所有現代瀏覽器都使用了標記-清除的垃圾回收方法,低版本 IE 采用的是引用計數方法.

標記整理算法

Mark-Compact (標記整理)算法正是為了解決標記清除所帶來的內存碎片的問題,提高對內存的利用.標記整理在標記清除的基礎進行修改,標記階段與標記清除相同,但是對未標記的對象處理方式不同,將其的清除階段變為緊縮極端.與標記清除是對未標記的對象立即進行回收,標記整理則將活着的對象向內存區的一段移動,移動完成后直接清理掉邊界外的內存.緊縮過程涉及對象的移動,所以效率並不是太好,但是能保證不會生成內存碎片.

doc


免責聲明!

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



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