首頁 > web前端 > js教程 > 了解 JavaScript 及其他語言中的垃圾收集

了解 JavaScript 及其他語言中的垃圾收集

Susan Sarandon
發布: 2025-01-27 22:32:17
原創
522 人瀏覽過

Understanding Garbage Collection in JavaScript and Beyond

最近,我在一次技術面試中被問到不同編程語言如何處理垃圾回收。這是一個令人驚訝卻又耳目一新的問題,它確實激起了我的興趣——我以前從未在面試中遇到過對內存管理如此深入的探討。我喜歡這個問題,並想在博客文章中進一步探討這個主題。


高效的內存管理對於高性能應用程序至關重要。 垃圾回收 (GC) 確保自動回收未使用的內存,防止內存洩漏和崩潰。在這篇文章中,我們將重點介紹垃圾回收在JavaScript中的工作方式,探討編程語言中使用的其他方法,並提供示例來說明這些概念。


什麼是垃圾回收?

垃圾回收是回收不再使用的對象所佔用的內存的過程。具有自動垃圾回收功能的語言會對這個過程進行抽象,從而使開發人員無需手動管理內存。例如,JavaScript 使用追踪式垃圾回收器,而其他語言則使用不同的技術。


JavaScript 中的垃圾回收

JavaScript 依賴於追踪式垃圾回收方法,特別是標記-清除算法。讓我們來分解一下:

1. 標記-清除算法

此算法確定內存中哪些對像是“可達的”,並釋放那些不可達的對象:

  1. 標記階段
    • 從“根”對象(例如,瀏覽器中的 window 或 Node.js 中的全局對象)開始。
    • 遍歷從這些根對象可以訪問的所有對象,並將它們標記為“存活”。
  2. 清除階段
    • 掃描堆並釋放未標記為可達的對象。

示例:

<code class="language-javascript">function example() {
  let obj = { key: "value" }; // obj 可达
  let anotherObj = obj; // anotherObj 引用 obj

  anotherObj = null; // 引用计数减少
  obj = null; // 引用计数减少到 0
  // obj 现在不可达,将被垃圾回收
}</code>
登入後複製
登入後複製
登入後複製

2. 分代垃圾回收

現代 JavaScript 引擎(例如 Chrome/Node.js 中的 V8)使用分代式 GC 來優化垃圾回收。內存被劃分為:

  • 新生代:短暫的對象(例如函數作用域變量)存儲在此處,並頻繁收集。
  • 老年代:長生命週期的對象(例如全局變量)存儲在此處,收集頻率較低。

為什麼分代式 GC 更高效?

  • JavaScript 中的大多數對像都是短暫的,可以快速收集。
  • 長生命週期的對像被移動到老年代,減少了頻繁掃描的需要。

其他垃圾回收策略

讓我們探討其他語言如何處理垃圾回收:

1. 引用計數

引用計數追蹤有多少引用指向一個物件。當引用計數降為 0 時,該物件將被釋放。

優點:

  • 簡單且立即回收記憶體。
  • 行為可預測。

缺點:

  • 循環引用:如果兩個物件互相引用,它們的計數將永遠不會達到 0。

範例:(Python 引用計數)

<code class="language-javascript">function example() {
  let obj = { key: "value" }; // obj 可达
  let anotherObj = obj; // anotherObj 引用 obj

  anotherObj = null; // 引用计数减少
  obj = null; // 引用计数减少到 0
  // obj 现在不可达,将被垃圾回收
}</code>
登入後複製
登入後複製
登入後複製

2. 手動記憶體管理

CC 這樣的語言要求開發人員明確地分配和釋放記憶體。

範例:(C 記憶體管理)

<code class="language-python">a = []
b = []
a.append(b)
b.append(a)
# 这些对象相互引用,但不可达;现代 Python 的循环收集器可以处理这种情况。</code>
登入後複製
登入後複製

優點:

  • 完全控制記憶體使用。

缺點:

  • 容易出現記憶體洩漏(忘記釋放記憶體)和懸空指標(過早釋放記憶體)。

3. 附循環收集器的追蹤式垃圾回收

一些語言(例如 Python)將引用計數循環檢測結合起來以處理循環引用。

  • 循環收集器定期掃描物件以偵測循環(從根物件無法存取的相互引用的物件群組)。一旦找到循環,收集器就會將其破壞並回收記憶體。
  • 循環收集器解決了純引用計數的最大缺點(循環引用)。它們增加了額外的開銷,但確保不會因為循環而導致記憶體洩漏。

4. Rust 的借用檢查器(無 GC)

Rust 採用了不同的方法,完全避免了垃圾回收。相反,Rust 透過借用檢查器強制執行嚴格的所有權規則:

  • 所有權:每個值一次只有一個擁有者。
  • 借用:您可以藉用引用(不可變或可變),但一次只允許一個可變引用,以防止出現資料競爭
  • 生命週期:編譯器推斷值何時超出作用域,自動釋放記憶體。

此系統確保記憶體安全,無需傳統的 GC,從而使 Rust 具有手動記憶體管理的效能優勢,同時有助於避免懸空指標等常見錯誤。

補充說明。 #資料競爭發生在並發或平行程式設計中,當兩個或多個執行緒(或進程)同時存取相同記憶體位置,並且至少一個執行緒寫入該位置時。由於沒有機制(例如鎖或原子操作)來協調這些並發訪問,因此共享資料的最終狀態可能不可預測且不一致——從而導致難以發現的錯誤。


垃圾回收策略比較

方法 语言 优点 缺点
引用计数 早期的 Python,Objective-C 立即回收,易于实现 循环引用失效
追踪式(标记-清除) JavaScript,Java 处理循环引用,对于大型堆效率高 停止世界暂停
分代式 GC JavaScript,Java 针对短暂的对象进行了优化 实现更复杂
手动管理 C,C 完全控制 容易出错,需要仔细处理
混合式(引用计数 循环收集器) 现代 Python 两全其美 仍然需要定期的循环检测
借用检查器 Rust 无需 GC,防止数据竞争 学习曲线较陡峭,所有权规则
---

JavaScript 如何處理常見場景

循環引用

JavaScript 的追蹤式垃圾回收器可以很好地處理循環引用:

<code class="language-javascript">function example() {
  let obj = { key: "value" }; // obj 可达
  let anotherObj = obj; // anotherObj 引用 obj

  anotherObj = null; // 引用计数减少
  obj = null; // 引用计数减少到 0
  // obj 现在不可达,将被垃圾回收
}</code>
登入後複製
登入後複製
登入後複製

事件監聽器和閉包

如果事件監聽器沒有正確清理,可能會無意中導致記憶體洩漏:

<code class="language-python">a = []
b = []
a.append(b)
b.append(a)
# 这些对象相互引用,但不可达;现代 Python 的循环收集器可以处理这种情况。</code>
登入後複製
登入後複製

重點總結

  1. JavaScript 使用帶有標記-清除演算法的追蹤式垃圾回收器來自動管理記憶體。
  2. 分代式 GC 透過專注於短暫的物件來最佳化效能。
  3. 其他語言使用不同的策略:
    • 引用計數:簡單但容易出現循環引用。
    • 手動管理:完全控制但容易出錯。
    • 混合方法:結合策略以獲得更好的效能。
    • Rust 的借用檢查器:無 GC,但有嚴格的所有權規則。
  4. 注意 JavaScript 中潛在的記憶體洩漏,尤其是在閉包和事件監聽器中。

這是一個深入了解語言用於垃圾回收策略的絕佳機會。我認為,了解垃圾回收的工作原理不僅可以幫助您編寫高效的程式碼,還可以讓您有效地調試與記憶體相關的錯誤。


參考文獻

  • JavaScript 與記憶體管理:MDN 文件
  • V8 垃圾回收:V8 部落格關於垃圾回收
  • Rust 的所有權:Rust 程式語言書籍
  • Java 垃圾回收:Oracle 文件
  • Python 的 GC:Python gc 模組

以上是了解 JavaScript 及其他語言中的垃圾收集的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板