2026年3月13日 星期五

V8引擎運作原理與垃圾回收機制

 

一樣是今天面是遇到的問題,沒答好,紀錄一下

一、 V8 引擎運作流程:從原始碼到機器碼

V8 引擎(Chrome 與 Node.js 的核心)並非單純的解釋器,它是一個複雜的 JIT (Just-In-Time) 編譯器

  1. 解析 (Parsing):將 JavaScript 原始碼轉化為 AST (抽象語法樹)

  2. 解釋 (Ignition):解釋器將 AST 轉為較低階的 Bytecode (位元組碼) 並立即執行。

  3. 編譯優化 (TurboFan):監控程式碼運行(Profiling),如果某段函式被頻繁呼叫(熱點程式碼),編譯器會將其轉換為優化過的 機器碼 (Machine Code),大幅提升速度。

  4. 去優化 (Deoptimization):如果輸入的資料型別突然改變(例如從 Int 變成 String),V8 會拋棄機器碼,退回到位元組碼執行,這就是效能損耗的來源。


二、 V8 垃圾回收機制 (Garbage Collection, GC)

V8 將記憶體分為 新生代 (Young Generation)老生代 (Old Generation),並採用不同的回收策略:

1. 新生代:Scavenge 演算法

  • 特性:存活時間短、頻繁回收。

  • 機制:將空間平分為 FromTo。新物件存入 From,回收時將存活物件複製到 To,然後清空 From 並角色對調。

  • 晉升 (Promotion):經歷兩次回收仍存活的物件,會被移動到老生代。

2. 老生代:Mark-Sweep & Mark-Compact

  • 特性:存活時間長、空間大。

  • 機制

    • 標記 (Marking):從根節點(如 windowglobal)出發,標記所有可達(Reachable)的物件。

    • 清除 (Sweeping):回收未被標記的記憶體空間。

    • 整理 (Compacting):將分散的物件移動到一塊連續空間,減少「記憶體碎片」。


三、 如何偵測與處理記憶體洩漏 (Memory Leak)

當物件不再被需要,但仍被「根節點」間接引用,導致 GC 無法回收時,就會發生記憶體洩漏。

1. 常見的洩漏場景

  • 全域變數:不小心宣告了 window.data = [...]

  • 未清除的計時器setInterval 內引用了外部變數,但從未調用 clearInterval

  • 閉包 (Closures):內部函式引用了外部大型變數,導致外部變數無法被回收。

  • 脫離文件的 DOM 節點:在 JS 中保留了對 DOM 的引用,即使該 DOM 已從頁面移除。

2. 實戰處理步驟

當你懷疑高併發頁面出現記憶體異常時:

  1. 使用 Chrome DevTools (Performance 面板)

    • 勾選「Memory」並錄製一段操作。

    • 觀察 Heap (堆疊) 曲線,若呈現「階梯式上升」且不會回落,即代表洩漏。

  2. 堆疊快照 (Heap Snapshot)

    • 進入 Memory 面板,點擊 Take snapshot

    • 執行可疑操作後,再拍一張快照。

    • 使用 "Comparison" (比較) 模式,找出在兩次快照之間「新增且未消失」的物件。

  3. 三點定位法

    • 拍下快照 A(初始狀態)。

    • 執行操作並拍下快照 B。

    • 再執行操作並拍下快照 C。

    • 檢查 B 與 C 之間持續增長的物件,這通常就是洩漏點。


四、 程式碼優化建議

  • 弱引用 (WeakMap / WeakSet):如果你需要建立物件與資料的關聯,但不希望影響 GC 回收,請優先使用 WeakMap

  • 解構賦值後手動置空:大型物件處理完後,設定 obj = null 斷開引用鏈。

  • 生命週期管理:在 React 的 useEffect 或 Vue 的 onUnmounted 中,務必移除 Event Listener 或清除 Timer。


沒有留言:

張貼留言