故事
- 跑 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