1. 程式人生 > 其它 >JS記憶體洩漏與垃圾回收機制

JS記憶體洩漏與垃圾回收機制

由於字串、物件和陣列沒有固定大小,所有當他們的大小已知時,才能對他們進行動態的儲存分配。JavaScript程式每次建立字串、陣列或物件時,直譯器都必須分配記憶體來儲存那個實體。只要像這樣動態地分配了記憶體,最終都要釋放這些記憶體以便他們能夠被再用,否則,JavaScript的直譯器將會消耗完系統中所有可用的記憶體,造成系統崩潰

這段話解釋了為什麼需要系統需要垃圾回收,js不像C/C++,他有自己的一套垃圾回收機制(Garbage Collection)。JavaScript的直譯器可以檢測到何時程式不再使用一個物件了,當他確定了一個物件是無用的時候,他就知道不再需要這個物件,可以把它所佔用的記憶體釋放掉了。例如:

var a = "before";
var b = "override a";
var a = b; //重寫a

這段程式碼執行之後,“before”這個字串失去了引用(之前是被a引用),系統檢測到這個事實之後,就會釋放該字串的儲存空間以便這些空間可以被再利用。

http://www.ssnd.com.cn 化妝品OEM代加工

垃圾回收原理

現在各大瀏覽器通常用採用的垃圾回收有兩種方法:標記清除引用計數

策略1:標記清除

這是javascript中最常用的垃圾回收方式。當變數進入執行環境是,就標記這個變數為“進入環境”。從邏輯上講,永遠不能釋放進入環境的變數所佔用的記憶體,因為只要執行流進入相應的環境,就可能會用到他們。當變數離開環境時,則將其標記為“離開環境”。

垃圾收集器在執行的時候會給儲存在記憶體中的所有變數都加上標記。然後,它會去掉環境中的變數以及被環境中的變數引用的標記。而在此之後再被加上標記的變數將被視為準備刪除的變數,原因是環境中的變數已經無法訪問到這些變量了。最後。垃圾收集器完成記憶體清除工作,銷燬那些帶標記的值,並回收他們所佔用的記憶體空間

垃圾收集器在執行的時候會給儲存在記憶體中的所有變數都加上標記

去掉環境中的變數以及被環境中的變數引用的變數的標記

此後再被加上標記的變數將被視為準備刪除的變數,因為環境中的變數已經無法訪問到這些變量了

策略2:引用計數

語言引擎有一張”引用表”,儲存了記憶體裡面所有資源(通常是各種值)的引用次數。如果一個值的引用次數是0,就表示這個值不再用到了,因此可以將這塊記憶體釋放。

上圖中,左下角的兩個值,沒有任何引用,所以可以釋放

const arr = [1,2,3,4];
console.log("hello world");

上面的程式碼中,陣列[1,2,3,4]是一個值,會佔用記憶體。變數arr是僅有的對這個值的引用,因此引用次數為1。儘管後面的程式碼沒有用到arr,它是會持續佔用記憶體

如果增加一行程式碼,解除arr對[1,2,3,4]引用,這塊記憶體就可以被垃圾回收機制釋放了。

let arr = [1,2,3,4];
console.log("hello world");
arr = null;

上面程式碼中,arr重置為null,就解除了對[1,2,3,4]的引用,引用次數變成了0,記憶體就可以釋放出來了。

因此,並不是說有了垃圾回收機制,程式設計師就輕鬆了。你還是需要關注記憶體佔用:那些很佔空間的值,一旦不再用到,你必須檢查是否還存在對它們的引用。如果是的話,就必須手動解除引用

再來下面來看看程式碼:

function problem() {
    var objA = new Object();
    var objB = new Object();

    objA.someOtherObject = objB;
    objB.anotherObject = objA;
}

在這個例子中,objA和objB通過各自的屬性相互引用;也就是說這兩個物件的引用次數都是2。在採用引用計數的策略中,由於函式執行之後,這兩個物件都離開了作用域,函式執行完成之後,objA和objB還將會繼續存在,因為他們的引用次數永遠不會是0。這樣的相互引用如果說很大量的存在就會導致大量的記憶體洩露

不過上面的問題也不是不能解決,我們可以手動切斷他們的迴圈引用。

myObj.element = null;
element.someObject =null;

這樣寫程式碼的話就可以解決迴圈引用的問題了,也就防止了記憶體洩露的問題。

缺點

如果存在迴圈引用的情況,那麼這個引用值佔的空間就永遠不會被回收。