「前端面試100問」之JavaScript 記憶體管理和垃圾回收機制
malloc()
之類的用於記憶體分配的原始函式,其中某些高階語言(如JavaScript)內建了垃圾收集器來完成此工作。 它跟蹤程式的記憶體分配,並分享程式是否不再使用分配的記憶體,然後將其自動釋放。 但是這種演算法無法完全決定是否需要記憶體。 因此,對於程式設計師而言,理解並確定特定程式碼段是否需要記憶體非常重要。
接下來我們一起來了解垃圾回收如何在JavaScript中工作。
垃圾收集
JavaScript引擎的垃圾收集器基本上會尋找從記憶體中刪除的無法訪問的物件。 這篇文章解釋以下兩種常見的垃圾收集演算法:
- 引用計數法
- 標記清除法
引用計數法
這是一個簡單的垃圾收集演算法。 該演算法查詢那些沒有被引用的物件,如果物件沒有附加引用,則可以進行垃圾回收。
var obj1 = {
property1: {
subproperty1: 20
}
};
複製程式碼
建立一個如上例所示的物件,以瞭解該演算法。
obj1
指向了一個物件,其property1
中還引用了另外一個物件。 由於obj1
引用了property1
指向的物件,因此此物件是不會被垃圾回收器進行回收的。
var obj2 = obj1;
obj1 = "some random text"
複製程式碼
現在,obj2
obj1
所引用的同一物件的引用,但是後來obj1
的值被更新為“some random text”,所以現在只有obj2
對引用著這個物件。
var obj_property1 = obj2.property1;
複製程式碼
接下來,obj_property1
引用obj2.property1
,該物件也包含一個物件。 因此,該物件現在具有兩個引用,如下所示:
- 作為
obj2
的屬性 - 變數
obj_property1
obj2 = "some random text"
複製程式碼
obj2
被更新為 “some random text” 之後,其也不再引用這個物件。 因此,似乎這個物件沒有了其餘的引用,因此可以被垃圾回收了。 但這可能是錯誤的說法,因為obj_property1
obj2.property1
的引用。 因此也不會被垃圾收集。
obj_property1 = null;
複製程式碼
現在,當我們從obj_property1
中刪除引用時,原來位於obj1
中的物件已完全沒有引用。 因此,現在可以對其進行垃圾收集了。
該演算法在哪些程式碼中會失效?
function example() {
var obj1 = {
property1 : {
subproperty1: 20
}
};
var obj2 = obj1.property1;
obj2.property1 = obj1;
return 'some random text'
}
example();
複製程式碼
在此,引用計數演算法不會在函式呼叫後從記憶體中刪除obj1
和obj2
,因為這兩個物件相互引用。
標記清除法
標記清除法會查詢從根(JavaScript的全域性物件)無法訪問的物件。
-
該演算法克服了引用計數演算法的侷限性。
-
沒有引用的物件將是不可訪問的,反之亦然。
var obj1 = {
property1: 35
}
複製程式碼
如上所示,我們可以看到建立的物件obj1
如何從ROOT
可達。
obj1 = null
複製程式碼
現在,當我們將obj1
的值設定為null
時,該物件不再可以從ROOT
到達,因此它將被進行垃圾回收。
預備知識
javascript中的記憶體管理是自動執行的,而且是不可見的
可達性:以某種方式可以訪問或者可以用的值,被保證儲存在記憶體中
根:有一些基本的固有可達值,由於顯而易見的原因無法刪除
本地函式的區域性變數和引數
當前巢狀呼叫鏈上的其他函式的變數和引數
全域性變數
其它...
可訪問性:如果引用或引用鏈可以從根訪問其他值,則認為該值是可訪問的
複製程式碼
標記-清除法 原理:
基本的垃圾回收演算法稱為“標記-清除”
垃圾回收器獲取根並“標記”它們
訪問並標記所有來自根的引用
訪問標記的物件並標記其引用。
以此類推,直到有未訪問的引用為止
除了標記物件,所有物件都被刪除
複製程式碼
讓我們通過檢視以下例項來嘗試和理解:
如上所示,這就是物件結構的樣子。 我們可以注意到從根目錄無法訪問的物件,但讓我們嘗試瞭解“標記並清除”演算法在這種情況下的工作方式。
演算法開始標記它從根開始可達的物件。 在上圖中,我們可以注意到在物件上標記的綠色圓圈。 以便將物件標識為從根可訪問。
那些從根目錄無法訪問的未標記的物件,它們將被垃圾收集。
注意
「垃圾的定義」
沒有被引用的物件或者有物件引用,但物件之間為相互引用,根訪問不到
複製程式碼
如上圖所示,儘管物件被引用了,但整體仍然是孤立的,最後仍然會被視為『垃圾』,並進行回收。
侷限性
必須使物件明確不可訪問,才會進行垃圾收集。
自2012年以來,JavaScript引擎針對引用計數垃圾收集演算法進行了修改。
補充:
JavaScript 引擎在垃圾回收方面的優化:
分代回收:物件分為“新物件”和“舊物件”,新物件出現,工作結束後就會被清理乾淨,存在時間長的物件,就會很少接受檢查
增量回收:將垃圾回收分解為多個部分,分別執行
空閒時間收集:垃圾回收器只在CPU空閒時執行
複製程式碼
謝謝閱讀。