JavaScript 記憶體管理及垃圾回收

Advoster 發表於 2023-01-22
JavaScript

一、記憶體管理

JavaScript 是一種自動垃圾回收語言,這意味著 JavaScript 引擎會自動監測和清理無用的記憶體。

JavaScript 中的記憶體管理主要由 JavaScript 引擎負責,開發人員不需要手動管理記憶體。JavaScript 引擎使用垃圾回收演算法來實現自動垃圾回收。

二、垃圾回收

JavaScript 垃圾回收演算法是指在 JavaScript 程式中,用來回收不再使用的記憶體的演算法。常見的垃圾回收演算法包括:

  • 標記-清除演算法:標記出所有不再使用的物件,然後清除它們。
  • 引用計數演算法:維護每個物件的引用計數,當計數為0時回收物件。
  • 標記-整理演算法:標記出所有不再使用的物件,然後將所有存活的物件整理到一起,回收其他物件。
  • 增量標記-整理演算法:將垃圾回收過程分成多個小步驟執行,並且可以處理迴圈引用問題。

現代 JavaScript 引擎通常採用增量標記-整理演算法或其他類似演算法來實現垃圾回收。

1、標記-清除演算法

標記-清除演算法是透過標記未使用的記憶體塊,然後清除這些標記的記憶體塊來實現垃圾回收的。

標記-清除演算法的工作流程如下:

  1. 從根節點開始,遍歷所有可達的物件,將其標記為“可用”。
  2. 掃描記憶體中所有物件,將未被標記的物件標記為“不可用”。
  3. 清除所有不可用物件佔用的記憶體。

標記-清除演算法的優缺點

優點:

  • 標記-清除演算法簡單易實現。
  • 標記-清除演算法可以回收任意型別的物件。

缺點:

  • 標記-清除演算法會產生碎片化的記憶體,這可能導致空間浪費。
  • 標記-清除演算法會產生暫停,這可能導致程式卡頓。

現在,由於標記-清除演算法會產生碎片化的記憶體和暫停,所以現代的 JavaScript 引擎主要使用增量標記-整理演算法來實現垃圾回收。增量標記-整理演算法將垃圾回收過程分成多個小步驟執行,避免了長時間的暫停。

標記-清除演算法是一種簡單易實現的垃圾回收演算法,但是會產生碎片化的記憶體和暫停,因此現在不再常用。

2、引用計數演算法

引用計數演算法是透過跟蹤每個物件的引用次數來確定物件是否被使用,如果一個物件的引用次數為0,則該物件被視為垃圾並被回收。

引用計數演算法的工作流程如下:

  1. 每當一個物件被引用時,將其引用計數增加1。
  2. 每當一個物件的引用被刪除時,將其引用計數減少1。
  3. 當一個物件的引用計數為0時,該物件被視為垃圾並被回收。

引用計數演算法的優缺點

優點:

  • 引用計數演算法可以實時回收垃圾。
  • 引用計數演算法可以較快地回收迴圈引用的物件。

缺點:

  • 引用計數演算法無法處理迴圈引用問題。如果兩個物件相互引用,而沒有其他變數引用它們,則它們的引用計數都為1,而它們都不能被回收。
  • 引用計數演算法會增加程式的執行時間和空間開銷。

引用計數演算法在處理迴圈引用問題上會有困難。而且引用計數演算法會增加程式的執行時間和空間開銷。因此現代的 JavaScript 引擎不再使用引用計數演算法來實現垃圾回收。

引用計數演算法的實現方式可以是各種各樣的, 例如:

  • 對於每一個物件都維護一個計數器,在有新的引用時將計數器加一,在引用結束時將計數器減一。
  • 對於每一個物件維護一個引用連結串列,在有新的引用時將引用的物件加入連結串列中,在引用結束時將引用的物件移除連結串列。

雖然現在的 JavaScript 引擎不再使用引用計數演算法來實現垃圾回收,但是對於引用計數演算法的理解對於理解其他演算法有很大幫助。

3、標記-整理演算法

標記-整理演算法是一種垃圾回收演算法,它首先標記出所有不再使用的物件,然後將所有存活的物件整理到一起,回收其他物件。

標記-整理演算法的工作流程如下:

  1. 標記:從根節點開始,遍歷所有可達的物件,將其標記為“存活”。
  2. 整理:將所有存活的物件移動到一起,以便進行回收。
  3. 回收:回收所有未被標記的物件佔用的記憶體。

標記-整理演算法的優缺點

優點:

  • 可以有效地處理迴圈引用問題。
  • 可以減少記憶體碎片化。

缺點:

  • 整理過程會影響效能。
  • 需要額外的空間來儲存活動物件和空閒物件。

標記-整理演算法在處理迴圈引用問題上會有優勢,減少記憶體碎片化,但是會影響效能,需要額外的空間來儲存活動物件和空閒物件。

標記-整理演算法是一種較為新的垃圾回收演算法,相對於標記-清除演算法和引用計數演算法而言,它可以更好地解決迴圈引用問題。

在使用標記-整理演算法進行垃圾回收時,系統會標記出所有仍然在使用的物件,然後將這些物件移動到一起,這樣就可以避免記憶體碎片化,並且可以減少迴圈引用問題的影響。

但是,標記-整理演算法也有缺點,整理過程會影響效能,需要額外的空間來儲存活動物件和空閒物件。另外,在 JavaScript 引擎中,這種演算法也沒有得到廣泛採用,大多數 JavaScript 引擎使用的是增量標記-整理演算法或其他類似演算法。

4、增量標記-整理演算法

現代的 JavaScript 引擎主要使用增量標記-整理演算法來實現垃圾回收,這種演算法在執行時將垃圾回收過程分成多個小步驟來執行,避免了長時間的暫停。

增量標記-整理演算法的工作流程如下:

  1. 標記:從根節點開始,遍歷所有可達的物件,將其標記為“存活”。
  2. 整理:將所有未被標記的物件移動到一起,以便進行回收。
  3. 回收:回收所有未被標記的物件佔用的記憶體。

增量標記-整理演算法透過將垃圾回收過程分成多個小步驟執行,來避免了長時間的暫停。這樣可以在不影響使用者體驗的情況下進行垃圾回收。

增量標記-整理演算法的優缺點

優點:

  • 避免了長時間的暫停,提高了程式的響應性。
  • 增量標記-整理演算法可以有效地處理迴圈引用問題。

缺點:

  • 由於增量標記-整理演算法是一種標記-整理演算法,所以會產生碎片化的記憶體,這可能降低記憶體利用率。
  • 增量標記-整理演算法的實現需要額外的空間來儲存活動物件和空閒物件。

增量標記-整理演算法是基於標記-清除演算法和標記-整理演算法的結合體。它首先使用標記-清除演算法找出所有存活的物件,然後使用標記-整理演算法將這些物件移動到一起,以便進行回收。

三、最佳化措施

JavaScript 中針對垃圾回收的最佳化措施有很多,主要有如下幾種:

  1. 避免迴圈引用迴圈引用是 JavaScript 垃圾回收中常見的問題,為了避免這種問題,開發人員應該儘量避免在不同物件之間建立迴圈引用關係。

  2. 儘早釋放不再使用的物件儘早釋放不再使用的物件可以減少垃圾回收的工作量,進而提高效能。例如,在不再使用的時候將變數賦值為 null 或 undefined,可以幫助 JavaScript 引擎更快地找到垃圾。

  3. 避免使用全域性變數全域性變數會一直存在,如果不需要使用,就應該儘早釋放。

  4. 避免使用長作用域鏈長作用域鏈會導致 JavaScript 引擎花費更多的時間來跟蹤物件的存活狀態,因此應該儘量避免使用長作用域鏈。

  5. 使用 WeakMap 和 WeakSetWeakMap 和 WeakSet 可以幫助我們維護物件之間的弱引用關係,可以減少迴圈引。

  6. 使用 requestIdleCallbackrequestIdleCallback 允許我們在瀏覽器空閒時執行一些任務,可以幫助我們在不影響使用者體驗的情況下進行垃圾回收。

 

需要注意的是,JavaScript 中的垃圾回收並不能保證程式一定不會出現記憶體洩漏的情況,例如迴圈引用,開發人員需要知道這種情況並採取對應的處理措施。

應對迴圈引用問題的處理措施?

JavaScript 中的迴圈引用是指兩個或多個物件之間相互引用的情況。這種情況下,這些物件就不能被垃圾回收機制正常回收,會導致記憶體洩漏。

解決迴圈引用問題主要有以下幾種方法:

  1. 使用 WeakMap 和 WeakSet:WeakMap 和 WeakSet 可以幫助我們維護物件之間的弱引用關係,可以減少迴圈引用問題。

  2. 使用計數器:對於某些情況,透過維護物件之間的引用計數可以幫助我們解決迴圈引用問題。

  3. 使用雙向連結串列:雙向連結串列可以幫助我們解決迴圈引用問題,可以支援物件之間相互引用,但是需要手動維護物件之間的關係。

  4. 避免迴圈引用:是最簡單而有效的解決辦法,開發人員應該儘量避免在不同物件之間建立迴圈引用關係。

  5. 使用第三方庫:使用第三方庫也可以幫助我們解決迴圈引用問題,如 cycle.js

  6. 使用設定空值的方法:在不再使用某個物件時,將其設定為空值可以消除對該物件的引用。

  7. 使用閉包:閉包可以在函式執行結束後銷燬其所引用的物件。

  8. 使用 IIFE:IIFE(立即執行函式表示式)可以在函式執行結束後立即銷燬其所引用的物件。

  9. 使用 WeakRef:WeakRef是一種弱引用物件,它不會影響到物件的存活狀態,可以使用它來消除迴圈引用。

需要注意的是, 在使用這些方法解決迴圈引用問題時, 還需要考慮到程式碼複雜度和可維護性, 在使用時應該慎重考慮。

 

JavaScript 中沒有強制垃圾回收的方法,也沒有手動釋放記憶體的方法, JavaScript 引擎會根據需要自動進行垃圾回收。

在 JavaScript 中,當一個物件不再被任何變數引用時,它就會被視為垃圾並被回收。需要注意的是,JavaScript 中的垃圾回收僅針對不再使用的記憶體,而不是不再使用的變數。例如,如果一個變數儲存的是物件的引用,則該物件可能不再被其他變數引用,但仍然可能被使用。

總之,JavaScript 中的記憶體管理主要由 JavaScript 引擎負責,開發人員不需要手動管理記憶體。JavaScript 中的垃圾回收是自動進行的,開發人員只需要瞭解垃圾回收機制並採取最佳化措施,就可以幫助程式更好的管理記憶體。