簡單梳理JavaScript垃圾回收機制

  • 2020 年 6 月 17 日
  • 筆記

JavaScript具有自動垃圾回收機制,即執行環境會負責管理程式碼執行過程中使用地記憶體。

這種垃圾回收機制的原理很簡單:找出那些不再繼續使用的變數,然後釋放其佔用的記憶體。為此,垃圾收集器會按照固定的時間間隔(或程式碼執行中預定的收集時間)周期性地執行這一操作。

對於函數中局部變數來說,其只在函數的執行過程中存在,在這個過程中,會為局部變數在堆(或棧)記憶體上分配相應的空間,以便存儲它們的值。然後在函數中使用這些變數,直至函數結束。此時局部變數沒有存在的必要了,可以釋放其記憶體。在這種情況下,容易判斷變數是否還有存在的必要,但是並非所有情況下都這麼容易得出結論。垃圾收集器必須跟蹤哪個變數有用哪個變數沒用,對於不再有用的變數打上標記,以備將來收回其佔用的記憶體。用於標識無用變數的策略具體到瀏覽器中的實現,通常有兩個策略。

  • 標記清除
  • 引用計數

1. 標記清除

標記清除是JavaScript中最常用的垃圾收集方式。當變數進入環境(例如在函數中聲明一個變數)時,就將這個變數標記為「進入環境」。邏輯上講,永遠不能釋放進入環境的變數所佔用的記憶體。因為只要執行流進入相應的環境,就可能會用到它們。當變數離開環境時,則將其標記為「離開環境」。

可以使用任何方式標記變數,如何標記變數並不重要,關鍵是採用何種策略。垃圾收集器在運行得時候會給存儲在記憶體中得所有變數都加上標記。然後,它會去掉環境中的變數及被環境中的變數引用的變數的標記。而在此之後再次被加上標記的變數將被視為準備刪除的變數(環境中的變數已經無法訪問到這些變數)。最後,垃圾收集器完成記憶體清除工作,銷毀那些帶標記的值並回收它們所佔用的空間。

2. 引用計數

引用計數是另一種不太常見的垃圾收集策略。引用計數的含義是跟蹤記錄每個值被引用的次數。當聲明了一個變數並將一個引用類型值賦給該變數時,則這個值的引用次數就是1.如果同一個值又被賦給另一個變數,則該值的引用次數加1.相反,如果包含對這個值引用的變數又取得了另外一個值,則這個值的引用次數減1.當這個值得引用次數變成0時,則說明沒有辦法再訪問這個值了。因此將其佔用得空間回收回來。這樣,當垃圾收集器下次再運行時,它就會釋放那些引用次數為零得值所佔用的記憶體。

Netscape Navigator 3.0是最早使用計數策略的瀏覽器,但很快就遇到一個嚴重的問題:循環引用。循環引用指:對象A中包含一個指向對象B的指針,而對象B也包含一個指向對象A的引用。它們的引用次數永遠不會是0。循環引用將導致記憶體無法回收

需要知道的是,IE中有部分對象不是原生JavaScript對象。例如,BOM和DOM中的對象就是使用C++以COM對象的形式實現的,而COM對象的垃圾收集機制採用的就是引用計數策略。因此,即使IE的JavaScript引擎使用標記清除策略實現,但是JavaScript訪問的COM對象依然基於引用計數策略。換句話說,只要在IE中涉及COM對象,就會存在循環引用的問題(IE9將BOM和DOM對象都轉換為真正的JavaScript對象,避免了兩種垃圾收集演算法並存導致的問題,也消除了常見的記憶體泄露現象)。

為避免類似的循環引用問題,最好在不適用它們的時候手工斷開原生JavaScript對象與DOM元素之間的連接。

myObject.element = null;
element.someObj = null;

3. 小結

本文主要是為了梳理JS垃圾回收機制所寫,闡述了JS垃圾回收機制的原理以及標記無用變數的策略(引用計數和標記清除),希望也能給小夥伴帶來幫助,梳理自己的知識體系。