All Articles

JS Memory Leak 簡單也不簡單

故事

  • 跑 AIQUA UI unit testing 的時候,發生記憶體不足的問題
FAIL src/.../HtmlCreative.test.js
  ● Test suite failed to run 

  ENOMEM: not enough memory
  • CI 環境上有 6GB 的記憶體,真的不夠嗎?

實驗與觀察

  • 我們在用的是 Jest 這款 task runner
  • 它有提供兩個指令 --runInBand & --logHeapUsage
  • --runInBand:只用一個 worker,一個測試跑完才跑下個測試
  • --logHeapUsage:輸出 heap memory 的使用量

實驗結果

PASS  src/.../JourneyChart.test.js (9.628s, 160 MB heap size)
PASS  src/.../ReachMethodNode.test.js (195 MB heap size)
...
PASS  src/.../validateGoalEvents.test.js (2588 MB heap size)
PASS  src/.../booleanString.test.js (2603 MB heap size)
(End)


其他人也遇到了一樣的問題嗎?

依賴反轉範例三
依賴反轉範例三

解決方案

  • 降版到 Jest 23 -> 失敗!
  • Add --expose-gc -> 成功 !?
  • --max-old-space-size -> 成功 !

--expose-gc

  • 執行 Node.js 時,在 global 物件底下新增 gc function
  • gc = garbage collect
  • 善意提醒 Node.js 清理 unused heap memory
  • process.memoryUsage()
  • Q: heapTotal & heapUsed 到底不同在哪邊?

為什麼加 --expose-gc 有用?

依賴反轉範例三

--max-old-space-size=SIZE

  • 當 unused heap memory 接近指定的用量的時候,V8 引擎會花更多時間在 garbage collection 上
 PASS  src/.../JourneyChart.test.js (6.902s, 135 MB heap size)
 PASS  src/.../CreativePanel.test.js (131 MB heap size)
 ...
 PASS  src/.../ScenarioToolbar.test.js (893 MB heap size)
 PASS  src/.../CreativeImagesAction.test.js (916 MB heap size)
 
 (everytime it reachs 1024MB, it will swipe ununsed memory)
 
 PASS  src/.../validateAudiencePlatform.test.js (180 MB heap size)
 PASS  src/.../validateHtmlCreative.test.js (186 MB heap size)
 ...
 (end)


兩種層級的 Memory Leak

  • Node.js 心裡有偵測到 unused memory 很多,但 Node.js 不說 不釋放
  • Node.js 真的沒偵測到那塊 unused memory
依賴反轉範例三

小結

  • Jest options:

    • --runInBand: 讓 Jest 只用一個 worker 跑測試,且一個跑完才跑下一個
    • --logHeapUsage: 輸出跑到每一個測試時的 heap 使用量
  • Node.js options:

    • --expose-gc: 讓 Node.js 的 global 變數有 gc 函數,用於要求做 garbage collection
    • --max-old-space-size=SIZE: 設定 unused 記憶體的上限,接近上限值時會較頻繁做 garbage collection

更進一步

  • 我們要如何偵測真的 Memory Leak ?
  • 上面提的都是 Node.js 的環境,Browser 環境會不同嗎?

Browser 環境

  • 沒有 global.gc() 可用,基本上沒有任何方法可以要求瀏覽器做 garbage collection
  • 查看 heap 使用量: console.memory / window.performance.memory
  • devtools

我們要如何偵測真的 Memory Leak?

  • 所謂 ”真的 Memory Leak“,代表的就是我們的 code 沒寫好,有一塊記憶體沒有釋放掉
  • 任何自動化的程式基本上無從得知那一塊沒被釋放掉的記憶體,到底是真的有要用,還是沒有
  • 不過,我們可以用工具提醒我們「可能」有 Memory Leak
  • 我們也可以盡可能知道什麼寫法會造成 Memory Leak

偵測可能的 Memory Leak 方法

  • node-memwatch: 連續 5 次 garbage collection,heap usage 仍然上升
  • BLeak: 在 browser 端自動化偵測 Memory Leak 的工具,有 24 頁的論文可以看
  • 3-snapshot technique: Google 當初解 Gmail website memory leak 的方法

    • Snapshot 1 -> do stuff -> snapsht 2 -> do same stuff -> snapshot 3 -> Compare snapshot 1, 2, 3
  • Detect memory leak by puppteer

哪些 Pattern 會造成 memory leak ?

  • 不小心用到全域變數
  • 忘記移除 timer
  • 忘記移除 event listener
  • closure

總結

  • 兩種層級的 memory leak
  • 沒有自動化的工具告訴我們是否真的有 memory leak ,以及 leak 在哪邊
  • 但 Chrome devtools 很實用
  • 我們也可以記得哪些 pattern 會有 memory leak

待探索

  • Chrome devtools 的 heap snapshot 功能
  • 自動化在 browser 端檢測 memory leak 的方法,並加入 e2e testing
  • react 上面會有 memory leak 的 pattern