鬼影追蹤 —— 發現 Node.js 中的記憶體洩漏
本文由碼農網 – 任琦磊原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃!
發現 Node.js 的記憶體洩露可能是一個不小的挑戰 —— 最近我們就有這樣一個經驗可以拿來分享。
我們客戶的一個微服務產生了如下圖所示的記憶體使用情況:
通過 Trace by RisingStack(一款 Node.js 效能監控和除錯工具)抓取到的記憶體使用情況
你可能會花費幾天的時間在這類東西上:剖析應用來查詢根源問題。本文中,我會來總結一下你能夠使用什麼工具以及如何使用他們,所以希望你能從中有所收穫。
“太長,勿看(TL;DR)”版本
在我們這個特定的情況中,服務正在一臺僅有 512MB 的小例項上執行。事實上,應用並沒有洩露任何記憶體,而是 GC 甚至沒有開始回收已取消引用的物件。
這為什麼會發生呢? 預設地,Node.js 會嘗試使用大約 1.5GB 的記憶體。因此當在記憶體比這小的系統上執行時,很有必要覆寫該記憶體使用預設值,因為垃圾回收是一個很消耗資源的操作。
它的解決方案是在 Node.js 執行時新增一個額外的引數:
node --max_old_space_size=400 server.js --production
可是,如果情況沒有像上例這樣明顯的話,你又如何來發現記憶體洩漏的問題呢?
理解 V8 的記憶體處理
在我們深入研究你能夠用來尋找和修復 Node.js 應用中記憶體洩漏問題的技術前,讓我們先來看一下 V8 是如何處理記憶體的。
定義
- 常住集大小(resident set size): 是記憶體中被程式佔用並保留的 RAM 部分,包括:
- 程式碼本身
- 棧
- 堆
- 棧(stack): 包含基本型別(primitive types)和物件的引用(references to objects)
- 堆(heap): 儲存引用型別(reference types),如物件,字串或閉包
- 物件的直接佔用記憶體(shallow size of an object): 物件本身自己直接佔用的記憶體空間
- 物件的佔用總記憶體(retained size of an object): 當物件與其關聯物件一起被刪除時釋放出的記憶體空間
垃圾回收器是如何工作的
垃圾回收是將應用已不再使用的物件佔用的記憶體進行回收的過程。通常來說,記憶體的分配相當容易,但當記憶體池(memory pool)已經耗盡需要回收記憶體時卻相當困難。
當根節點不可達某個物件時,它便進入了垃圾回收的候選名單了,所以不要被根物件或其它任意有效物件引用。根物件可以是全域性物件,DOM 元素或區域性變數。
堆兩個主要的區段,新生代空間(New Space)和老生代空間(Old Space)。新生代空間用於新的記憶體分配,一般在約為 1-8MB 左右,所以這裡的垃圾回收很快。在新生代空間中的物件被稱為新生代(Young Generation)。老生代空間則存放那些免於回收從新生代空間晉升至此的物件——它們被稱為老生代(Old Generation)。老生代空間分配記憶體很方便但回收卻很困難,所以垃圾回收很少在這裡執行。
垃圾回收為什麼會變得如此困難? V8 JavaScript 引擎採用了“停止一切(stop-the-world)”垃圾回收器機制。實際使用中,這意味著垃圾回收處理過程中程式會停止執行。
通常,約 20% 的新生代會留下來進入老生代。只有到了記憶體耗盡的時候才會開始回收老生代空間的記憶體。這些 V8 引擎是通過使用兩種不同的回收演算法來實現的:
- Scavenge 回收,快速且執行在新生代的回收上。
- Mark-Sweep 回收,較慢且執行在老生代的回收上。
更多關於這如何工作的資訊可以參考文章 V8 之旅:垃圾回收。更過關於整體記憶體管理的資訊,可訪問記憶體管理參考。
尋找 Node.js 記憶體洩漏時你可以使用的工具/技術
heapdump 模組
你可以通過使用 heapdump 模組來建立一個堆的快照以便日後檢查。把它新增到你的專案很簡單:
npm install heapdump --save
然後在你的進入點(entry point)只要新增:
var heapdump = require('heapdump');
當你完成了上述操作,你就可以開始收集 heapdump 了,你可以通過使用命令 $ kill -USR2 <pid>,或者通過呼叫:
heapdump.writeSnapshot(function(err, filename) { console.log('dump written to', filename); });
一旦你獲取了你的快照,就是時候讓它們發光發熱了。你最好確保你捕獲了不同時間的多個快照,這樣你就可以將它們進行比較了。
谷歌 Chome 開發者工具
首先你需要將你的記憶體快照載入進 Chrome 分析器。方法為:開啟 Chrome 開發者工具,進入 Profiles,然後 載入 你的堆快照。
當你完成載入後,它應該看起來像這樣:
到目前為止一切正常,但這個截圖裡到底能看出些什麼東西呢?
這裡需要注意的一個重要的事情是已選中的檢視視窗:Comparison。這個模式允許你來比較兩個(或多個)不同時間獲取的堆快照,所以你能夠準確地找出哪些物件分配到了記憶體,與此同時哪個的沒有釋放。
另一個重要的標籤是 Retainers。它用來展示到底為什麼一個物件不可以被垃圾回收掉,是什麼仍然引用著它。這種情況下,全域性變數 log會保持一個到這個物件本身的引用,以防止垃圾回收器釋放其資源。
底層工具
mdb
mdb 工具是一個用於對作業系統,系統故障轉儲,使用者程式,程式資訊轉儲和物件檔案底層的除錯和編輯的可擴充套件工具。
gcore
生成正在執行中的程式的資訊轉儲,包括程式 ID pid。
放在一起
首先我們需要建立一個轉儲用來研究。你可以簡單地實現:
gcore `pgrep node`
在你獲得之後,你可以通過下面的命令搜尋堆中全部的 JS 物件:
> ::findjsobjects
當然,你需要獲取連續的資訊轉儲來比較轉儲間的不同。
一旦你發現了可疑的物件,你可以這樣分析它們:
object_id::jsprint
現在你所需要做的便是尋找物件(根節點)的持有者(retainer)。
object_id::findjsobjects -r
這個命令將會返回持有者的 id。然後你可以再次使用 ::jsprint 來分析這些持有者。
你可以通過觀看來自 Netflix 的 Yunong Xiao 的講座來了解關於如何使用它的更詳細的版本。
推薦閱讀
- MDB 和 Node.js(MDB and Node.js)
- MDB 和 Linux(MDB and Linux)
- Netflix 生產環境中除錯 Node.js(Debug Node.js in Production at Netflix)
- 沃爾瑪的 Node.js 記憶體溢位(Walmart Node.js Memory Leak)
- Trace —— 視覺化的微服務監控工具(Trace – Visualized Microservice Monitoring Tool)(廣告)
你有關於 Node.js 記憶體洩漏更多的想法或見解嗎?在評論中分享一下吧。
譯文連結:http://www.codeceo.com/article/nodejs-memory-leak.html
英文原文:Hunting a Ghost - Finding a Memory Leak in Node.js
翻譯作者:碼農網 – 任琦磊
[ 轉載必須在正文中標註並保留原文連結、譯文連結和譯者等資訊。]
相關文章
- Node.js 中記憶體洩漏分析Node.js記憶體
- 如何定位 Node.js 的記憶體洩漏Node.js記憶體
- vue使用中的記憶體洩漏Vue記憶體
- [譯] Swift 中的記憶體洩漏Swift記憶體
- Android中的記憶體洩漏Android記憶體
- 記憶體洩漏記憶體
- 分析記憶體洩漏和goroutine洩漏記憶體Go
- 使用mtrace追蹤JVM堆外記憶體洩露JVM記憶體洩露
- 記憶體洩漏的原因記憶體
- 如何避免JavaScript中的記憶體洩漏?JavaScript記憶體
- Android中的記憶體洩漏模式Android記憶體模式
- Swift 閉包中的記憶體洩漏Swift記憶體
- 介紹Java中的記憶體洩漏Java記憶體
- 關於java中的記憶體洩漏Java記憶體
- 小心遞迴中記憶體洩漏遞迴記憶體
- js記憶體洩漏JS記憶體
- Java記憶體洩漏Java記憶體
- webView 記憶體洩漏WebView記憶體
- Javascript記憶體洩漏JavaScript記憶體
- 【轉】java中的記憶體溢位和記憶體洩漏Java記憶體溢位
- 當出現記憶體洩漏的時候記憶體
- [譯]理解閉包中的記憶體洩漏記憶體
- JavaScript中的垃圾回收和記憶體洩漏JavaScript記憶體
- 翻譯 | 理解Java中的記憶體洩漏Java記憶體
- 如何檢查Javascript中的記憶體洩漏JavaScript記憶體
- 深入理解Java中的記憶體洩漏Java記憶體
- 如何識別Java中的記憶體洩漏Java記憶體
- WebView引起的記憶體洩漏WebView記憶體
- ARC下的記憶體洩漏記憶體
- 【轉】Java的記憶體洩漏Java記憶體
- Java應用程式中的記憶體洩漏及記憶體管理Java記憶體
- 記憶體分析與記憶體洩漏定位記憶體
- 記憶體洩漏和記憶體溢位記憶體溢位
- valgrind 記憶體洩漏分析記憶體
- Android 記憶體洩漏Android記憶體
- Android記憶體洩漏Android記憶體
- 淺談記憶體洩漏記憶體
- JavaScript 記憶體洩漏教程JavaScript記憶體