Node.js內存泄漏分析
來自: http://blog.csdn.net/dan_blog/article/details/50755117
在極客教育出版了一個視頻是關于《Node.js 內存泄漏分析》,本文章主要是從內容上介紹如何來處理Node.js內存異常問題。如果希望學習可前往極客學院:
本文章的關鍵詞
- 內存泄漏
- 內存泄漏檢測
- GC分析
- memwatch</blockquote>
文章概要
由于內存泄漏在Node.js中非常的常見,可能在瀏覽器中應用javascript時,對于其內存泄漏不是特別敏感,但作為服務器語言運行時,你就不得不去考慮這些問題。由于很小的邏輯可能導致服務器運行一天或者一個星期甚至一個月才會讓你發現內存不斷上漲,而終于會到那天你不得不重啟服務來保護服務器的性能,那么這種問題就有必要在上線前進行一個系統檢測,同時在上線后能夠有一個有效的監控程序來保證運行安全。
什么是內存泄漏
在介紹Node.js內存泄漏前,我們應該首先知道什么才是內存泄漏,內存泄漏又包含哪些類型。
內存泄漏概念
內存泄漏也稱作“存儲滲漏”,用動態存儲分配函數,動態開辟的空間,在使用完畢后未釋放,結果導致一直占據該內存單元,直到程序結束。
上面的定義來自百度百科,當然從上面的定義我們就可以了解到內存泄漏簡單說,就是占用著系統資源而一直不釋放,所導致的系統資源越來越少,從而導致系統異常的一個比較嚴重的問題,可以使用下面來解釋內存泄漏。
Node.js服務程序假定是一次“班級大掃除”,系統內存資源假定為班級的資源“五個掃把”,而利用資源進行工作的“學生”,這里我們假定為進程。當天學校要進行大掃除,每個班級只有五把掃把,每個人都需要完成一部分掃地工作,學生完成后自動給其他人,當所有人完成掃地工作,大掃除結束,老師首次會將掃把分配給五個人,但是這五個人中存在幾個同學手握掃把不做事,即使做完事了,也不會將掃把分配給其他人,導致其他人的掃地工作一直無法進行下去,或者由于掃把有限導致打掃工作進行的很漫長。內存泄漏類型
內存泄漏包含的類型有:常發性、偶發性、一次性、隱式。
常發性
發生內存泄漏的代碼會被多次執行到,每次被執行的時候都會導致一塊內存泄漏。偶發性
發生內存泄漏的代碼只有在某些特定環境或操作過程下才會發生。常發性和偶發性是相對的。對于特定的環境,偶發性的也許就變成了常發性的。一次性
發生內存泄漏的代碼只會被執行一次,或者由于算法上的缺陷,導致總會有一塊且僅一塊內存發生泄漏。比如,在類的構造函數中分配內存,在析構函數中卻沒有釋放該內存,所以內存泄漏只會發生一次。隱式
其主要是在調用函數或者模塊時,當參數或者輸入沒有達到界定值時,是不會發生泄漏,當參數或者輸入值達到一定時,才會發現內存泄漏,我們稱這種為隱式。程序在運行過程中不停的分配內存,但是直到結束的時候才釋放內存。嚴格的說這里并沒有發生內存泄漏,因為最終程序釋放了所有申請的內存。但是對于一個服務器程序,需要運行幾天、幾周甚至幾個月,不及時釋放內存也可能導致最終耗盡系統的所有內存。所以,我們稱這類內存泄漏為隱式內存泄漏。隱式才是我們本文中所需要去探索,去發現和解決的異常問題。Node.js內存泄漏會帶來的危害
Node.js內存泄漏到底會有哪些危害,既然我們希望去發現和檢測內存泄漏,那么我們就必須要首先知道Node.js內存泄漏到底會影響哪些問題。
用戶服務異常
一般情況下用戶是無法察覺內存泄漏帶來的影響,但是對于有些情況下,因為內存泄漏可能導致用戶響應很慢,這種情況下對于用戶而言無法感受到異常,但是可以普遍感受到服務響應變慢,而且這種情況可能會導致新注冊用戶丟失等問題。
服務器性能異常
一般情況下,內存泄漏直接的影響就是服務器,服務器會因為內存的不斷上漲,從而系統資源可使用的空間越來越小,這樣就會慢慢的導致該服務影響到服務器中其他的一些基礎服務的運行,從而導致服務器越來越慢,同時導致可使用資源越來越少,直接導致服務器資源耗盡,服務器異常,導致數據丟失等等,比較嚴重的問題。
常見的 Node.js 內存泄漏問題
這里主要介紹兩種關于內存泄漏的代碼邏輯,主要是循環引用和無節制循環帶來的內存泄漏。
循環引用
這部分在javascript中可能更比較常見,其主要介紹的是說:A對象包含一個指向B的指針,對象B也包含一個指向A的引用。 這就可能造成大量內存得不到回收(內存泄露),因為它們的引用次數永遠不可能是 0 ,因此作為回收機制是不會將A和B進行回收的。
var func = function () {} var el = function () {} el.func = func; func.element = el;例如上面代碼中el和func互相引用。而且這種類型的內存泄漏可以說是常發性。
無節制循環
沒有對數組有任何限制,并且在數組過大時,沒有進行有效的回收處理機制。
模塊中的私有方法和屬性
任意編寫的模塊文件中,均會在頭和尾部上添加字符串,以形成閉包,然后在require的過程中被調用一次,并且將exports對象存儲在內存中,直到進程退出才會回收。這種嚴格上說并非內存泄漏,只是說需要嚴格注意模塊中的私有變量和方法的使用,避免因為過多的私有變量占用到了過多的系統內存。
假定我們有模塊leak.jsvar leakArray = []; exports.leak = function () { leakArray.push("leak" + Math.random()); };那么如果我們創建一個test.js來引用該模塊,運行時,則會看的leakArray一直變大。
var Mod = require('./leak'); Mod.leak(); Mod.leak(); Mod.leak(); Mod.leak(); Mod.leak();如果這里我們無節制的調用方法leak時,就可能導致數組過大,從而導致一定的問題。
過大的數組循環
先看下如下代碼:for ( var i = 0; i < 100000000; i++ ) { var user = {}; user.name = 'outmem'; user.pass = '123456'; user.email = 'outmem[@outmem](/user/outmem).com'; }這段代碼最主要的原因在于循環太大,直接內存分配到超過v8內存限制數量。由于JavaScript事件循環的執行機制,這段代碼沒有機會進入下一個事件循環。用setInterval和setTimeout可以進入下一個循環。但是不推薦用setInterval和setTimeout。對于大循環代碼,建議最好是分割,然后進行處理,分段進行處理。因為每次都沒有效利用好一次循環。一次事件循環,不要超過10ms。
Node.js內存泄漏工具使用實踐
這里主要介紹一些常見的Node.js內存泄漏檢測工具,并且針對其中的memwatch以及heapdump來進行詳細的實踐學習。
Node.js內存泄漏工具
node-inspector提供了綁定在Node中的V8分析器和一個基于WebKit Web Inspector的debug界面,大家可以看下這篇博文,其中就是介紹如何應用該工具來檢測內存泄漏
http://www.cnblogs.com/ldlchina/p/4762036.htmlnode-mtrace,它使用了GCC的mtrace工具來分析堆的使用。
https://github.com/Jimbly/node-mtrace
但是該工具提供的是點對點的,如果存在異步函數的話,會比較麻煩,這種可以結合其他工具一起使用會比較方便,大家可以看下github的示例代碼node-heap-dump對V8的堆抓取了一張快照并把所有的東西序列化進一個巨大的JSON文件。它還包含了一些分析研究快照結果的JavaScript工具。這里在memwatch中我們是會應用該工具相應的功能來定位泄漏代碼邏輯。
memwatch
是一個專門用來檢測和監控內存泄漏的工具,其不僅僅可以在上線前進行掃描監控,也可以在上線后進行有效的內存泄漏檢測。
接下來的話,我們就實踐應用memwatch來檢測內存泄漏的以及通過heapdump抓取GC,進行GC內存分析實踐。memwatch的實踐
在學習memwatch之前,首先需要安裝配置相應的模塊,具體操作可以使用npm install memwatch,下載該模塊的時候需要進行編譯,因此需要python2.6以上以及需要VS2012以上。如果以上配置遇到問題的時候,可以在我的資料中尋找一篇關于如何解決這兩個問題的博文鏈接http://blog.csdn.net/dan_blog/article/details/50707278
那么接下來我們就來實踐應用該模塊,首先我們看一下簡單的實例代碼:var http = require('http'); var server = http.createServer(function (req, res) { for (var i=0; i<1000; i++) { server.on('request', function leakyfunc() {}); } res.end('Hello World\n'); }).listen(1337, '127.0.0.1');從代碼本身看其是存在一定的問題,隨著用戶每一次請求,其內存占用都會提供,我們停止請求一段時間時,其內存也不會降低,說明該段代碼存在一定的問題。
大家可以前往極客學院視頻地址中下載該代碼的源碼,其中還包括了其他資料
可以運行下該段代碼,如果使用http://127.0.0.1:1337不斷的進行訪問,如果在windows下可以在進程中查看該進程內存的使用情況
![]()
如果你是在Linux的話,可以首先通過命令查看該進程ID,然后再使用top -p 進程IDps -ef | grep node top -p 12202隨時的查看進程所占用的內存,通過訪問你會看到其內存的變化情況,同時發現其內存并不會慢慢釋放回來。
既然出現了上面的內存泄漏,那么我們就使用memwatch以及heapdump來做檢測和分析,改進后的代碼如下。var http = require('http'); var memwatch = require('memwatch'); var hd = new memwatch.HeapDiff(); var heapdump = require('heapdump'); var server = http.createServer(function (req, res) { for (var i=0; i<1000; i++) { server.on('request', function leakyfunc() {}); } res.end('Hello World\n'); }).listen(1337, '127.0.0.1'); memwatch.on('leak', function(info) { var diff = hd.end(); console.log(JSON.stringify(diff));var file = '/tmp/myapp-' + process.pid + '-' + Date.now() + '.heapsnapshot'; heapdump.writeSnapshot(file, function(err){ if (err) console.error(err); else console.error('Wrote snapshot: ' + file); });
}); server.setMaxListeners(0); console.log('Server running at http://127.0.0.1:1337/. Process PID: ', process.pid);</pre>
上面的邏輯中包含了使用memwatch來檢測內存泄漏,同時還包含了使用heapdump來抓取內存的實時情況,通過運行如上代碼,然后使用壓測工具對http://127.0.0.1:1337進行壓測,當壓測到一定的情況后,在運行窗口你可以看的其內存泄漏的提醒,并在這時候會在file這個目錄文件中(如果在windows中最好就修改下file這個文件路徑,這里的示例代碼是相對Linux環境的)記下當前內存泄漏時的內存GC情況。
GC內存分析
既然獲取到該文件后,我們再使用google chrome的工具來分析GC的內存情況,并查看出導致內存泄漏的原因。
![]()
打開工具后,再使用Load,將我們剛生成的文件加載進去。
加載進去以后關于分析方法,大家可以參考該文章
http://itindex.net/detail/52929-chrome-%E5%BC%80%E5%8F%91-%E5%B7%A5%E5%85%B7
里面有詳情的介紹,當然如果想了解更具體的分析方法,大家可以去極客學院上查看本課程的視頻學習資料。總結
這就是本文所介紹的知識,在看完本文以后,大家至少了解什么是內存泄漏,Node.js的內存泄漏會導致哪些問題,以及如何應用memwatch和heapdump來檢測和分析內存泄漏問題,同時需要簡單了解Chrome中的內存分析工具的使用。
</div>