在一般的后端語言中线罕,基本的內(nèi)存使用上基本沒有什么限制钞楼,但是在nodeJs中卻只能使用部分內(nèi)存询件。在64位系統(tǒng)下位約為1.4G唆樊,在32位系統(tǒng)下約為0.7G,造成這個(gè)問題的主要原因是因?yàn)閚odeJs基于V8構(gòu)建嘿辟,V8使用自己的方式來管理和分配內(nèi)存片效,這一套管理方式在瀏覽器端使用綽綽有余,但是在nodeJs中這卻限制了開發(fā)者昙读,在應(yīng)用中如果碰到了這個(gè)限制舌缤,就會(huì)造成進(jìn)程退出。
戴著腳銬跳舞
在服務(wù)端陵吸,假如將一個(gè)2G的文本文件讀到內(nèi)存中介牙,node進(jìn)程就會(huì)進(jìn)程崩潰,雖然這種情況不常見囚似,但是開發(fā)時(shí)候也只能小心翼翼线得,該如何解決呢?
暴力加內(nèi)存
在啟動(dòng)node進(jìn)程的時(shí)候募狂,可以調(diào)整內(nèi)存大小角雷。
node --max-old-space-size=1700 test.js // 單位為MB
node --max-new-space-size=1024 test.js // 單位為KB
這個(gè)在初始化進(jìn)程的時(shí)候就生效勺三,而且不能動(dòng)態(tài)擴(kuò)容,一般用來擴(kuò)充內(nèi)存祈远,以免稍微多一些內(nèi)存就崩潰商源。
合理利用內(nèi)存
V8限制內(nèi)存用量,主要是因?yàn)閂8垃圾回收機(jī)制的設(shè)計(jì)躬充。也正是因?yàn)槔厥諜C(jī)制讨便,我們才不需要像c++那樣手動(dòng)回收。
在V8中伴找,JavaScript的對(duì)象是分配在堆內(nèi)存中废菱,使用process.memoryUsage()
可以查看當(dāng)前內(nèi)存使用狀態(tài)《队現(xiàn)在內(nèi)存有了最大的限制袒炉,那么我們寫代碼的時(shí)候就要額外注意了樊零,要避免濫用內(nèi)存。
1.及時(shí)釋放全局變量
global.a = {name: 'a object'};
console.log(global.a); // 全局變量不會(huì)再用到
global.a = undefined; //釋放該對(duì)象
- 合理使用閉包夺艰,及時(shí)釋放閉包函數(shù)的引用郁副。
在啟動(dòng)node進(jìn)程時(shí)帶上--trace_gc
會(huì)在V8進(jìn)行垃圾回收的時(shí)候打印日志豌习,可以幫助我們觀察程序內(nèi)部垃圾回收的情況。
內(nèi)存泄漏
內(nèi)存泄漏的實(shí)質(zhì)就是應(yīng)該被垃圾回收的對(duì)象出現(xiàn)意外愕贡,沒有被回收巷屿,變成常駐于內(nèi)存中的對(duì)象。
通常造成內(nèi)存泄漏的原因有如下幾個(gè):
使用內(nèi)存進(jìn)行緩存
隊(duì)列消費(fèi)不及時(shí)
作用域未釋放
謹(jǐn)慎使用緩存
緩存和之前說的全局變量其實(shí)是一回事憨琳,平時(shí)前端JavaScript使用緩存的場景可能是因?yàn)閍pi請求慢旬昭,于是聲明一個(gè)全局變量來緩存數(shù)據(jù),如果有緩存就不用再發(fā)請求了遍略,節(jié)約了時(shí)間骤坐。但是在nodeJs中,緩存并非物美價(jià)廉蕾久。
lodash的memoize方法僧著,提供了一種緩存策略,利用一個(gè)map的鍵值對(duì)來緩存數(shù)據(jù)
function memoize(func, resolver) {
if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) {
throw new TypeError('Expected a function')
}
const memoized = function(...args) {
const key = resolver ? resolver.apply(this, args) : args[0]
const cache = memoized.cache
if (cache.has(key)) {
return cache.get(key)
}
const result = func.apply(this, args)
memoized.cache = cache.set(key, result) || cache
return result
}
memoized.cache = new (memoize.Cache || MapCache)
return memoized
}
這種方法如果不做限制盹愚,或?qū)е戮彺鎸?duì)象過大,一直占用在內(nèi)存中霞篡,使用需謹(jǐn)慎端逼。
關(guān)注隊(duì)列
node底層的事件循環(huán)污淋,會(huì)不停查看是否有事件待處理,如果有的話礁鲁,就取出事件以及相關(guān)回調(diào)函數(shù)赁豆,如果有回調(diào)函數(shù)則執(zhí)行。
事件循環(huán)一個(gè)典型的生產(chǎn)者/消費(fèi)者的形式析二,異步I/O是事件的生產(chǎn)者叶摄,事件循環(huán)就是消費(fèi)者。大部分時(shí)候消費(fèi)速度都是大于生產(chǎn)速度的蛤吓,但當(dāng)生產(chǎn)速度大于消費(fèi)速度的時(shí)候会傲,事件會(huì)產(chǎn)生堆積拙泽,回調(diào)函數(shù)中的作用域不會(huì)得到釋放,于是產(chǎn)生了內(nèi)存泄漏奔滑。
例如現(xiàn)在往數(shù)據(jù)庫插入100W條數(shù)據(jù)朋其,數(shù)據(jù)庫建立在文件系統(tǒng)之上脆炎,寫數(shù)據(jù)效率很慢秒裕,很容易產(chǎn)生過多事件等待處理,這時(shí)候內(nèi)存泄漏就無法避免了几蜻。
解決方案:
監(jiān)控隊(duì)列長度
為異步請求加上超時(shí)機(jī)制梭稚。