js垃圾回收

內存生命週期

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

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

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);

經過函數調用分配內存

有些函數調用結果是分配對象內存:java

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 的結果

使用值

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

垃圾回收

程序是運行在內存裏的,當聲明一個變量、定義一個函數時都會佔用內存.內存的容量是有限的,若是變量、函數等只有產生沒有消亡的過程,那早晚內存有被徹底佔用的時候.這個時候,不只本身的程序沒法正常運行,連其餘程序也會受到影響.因此,在計算機中,咱們須要垃圾回收.須要注意的是,定義中的「自動」的意思是語言能夠幫助咱們回收內存垃圾,但並不表明咱們不用關心內存管理,若是操做失當,JavaScript 中依舊會出現內存溢出的狀況.數組

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

引用

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

在這裏,「對象」的概念不只特指 JavaScript 對象,還包括函數做用域(或者全局詞法做用域).post

變量

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

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

引用計數垃圾收集

這是最初級的垃圾收集算法.此算法把「對象是否再也不須要」簡化定義爲「對象有沒有其餘對象引用到它」.若是沒有引用指向該對象(零引用),對象將被垃圾回收機制回收.

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

相關文章
相關標籤/搜索