Node.js 效能分析:偵測 Memory Leak 與 CPU 瓶頸

Node.js 以其卓越的非阻塞 I/O 著稱,但不良的程式慣例仍會導致 記憶體洩漏 (Memory Leak)CPU 密集型阻塞,進而讓伺服器回應變慢、佔用率飆升甚至因 OOM (Out of Memory) 崩潰。

什麼是 Memory Leak?

當你分配了記憶體但不再需要它,而垃圾回收機制 (GC) 卻認為該資料仍被引用而無法回收時,洩漏就發生了。

常見洩漏兇手:

  1. 意外的全域變數:不小心將資料掛載到 global 物件上。
  2. 被遺忘的定時器setInterval 啟動後從未被 clearInterval
  3. 閉包與大物件:閉包內引用了大型物件,且該閉包一直存在於記憶體中。
  4. 事件監聽器未移除:對同一個物件反覆 .on() 卻從未 .off()

使用 Chrome DevTools 診斷

Node.js 內建了偵錯協議,讓你使用熟悉的 Chrome 工具進行效能剖析:

  1. 啟動偵錯模式
    node --inspect app.js
    
  2. 開啟工具:在 Chrome 網址列輸入 chrome://inspect,點擊 "Open dedicated DevTools for Node"

記憶體快照 (Heap Snapshot)

Memory 分頁,你可以點擊 Take Snapshot

  • 技巧:拍下一張快照,執行一些操作,再拍第二張。使用 "Comparison" 模式,找出那些「只增不減」的物件。

CPU 火燄圖 (Flame Graph)

Profiler 分頁,點擊 Start 並在你的 API 上進行壓力測試,完成後點擊 Stop

  • 尋找佔用比例最高的「寬長方形」,那通常就是消耗 CPU 最兇猛的程式碼熱點 (Hot Spot)。

程式碼級監控:process.memoryUsage()

你可以定期輸出記憶體指標來偵測趨勢。如果 rssheapUsed 持續線性上升,就是洩漏的徵兆。

setInterval(() => {
  const usage = process.memoryUsage();
  console.log(
    `[記憶體監控] RSS: ${Math.round(usage.rss / 1024 / 1024)}MB | HeapUsed: ${Math.round(usage.heapUsed / 1024 / 1024)}MB`
  );
}, 10000);

指標說明:

  • RSS (Resident Set Size):進程佔用的總實體記憶體(包含所有堆疊與 C++ 資源)。
  • heapUsed:V8 引擎實際使用的記憶體空間。
  • external:V8 管理的 C++ 物件佔用的記憶體(例如 Buffer)。

監控垃圾回收 (GC)

如果你懷疑 GC 頻率過高導致效能下降,可以使用以下指令啟動:

node --trace-gc app.js

這會在控制台即時輸出每次 GC 的耗時與空間回收情況。

總結

  1. 建立基準線 (Baseline):在部署前先了解正常的記憶體占用情況。
  2. 善用 Chrome DevTools 的「快照比對」來精確定位洩漏來源。
  3. 對於持續上升的 rss 數值要保持高度警覺。