《深入淺出Node.js》內(nèi)存控制

依托 V8 的 NODE 在使用內(nèi)存時是有大小限制的,具體大小與系統(tǒng)類型肥印、版本识椰、NODE 版本相關(guān)(64 位系統(tǒng) 1.4GB 和 32 位系統(tǒng) 0.7 GB 的大小)深碱。

NODE 與 C++不同腹鹉,垃圾回收 GC 由系統(tǒng)自動執(zhí)行,不由開發(fā)者參與莹痢,當(dāng)代碼使用不當(dāng)時种蘸,會導(dǎo)致 GC 不能正確回收發(fā)生內(nèi)存泄漏

V8 對象(內(nèi)存)分配 及回收

> node --max-old-space-size=1700 test.js // 單位為MB竞膳,老生代內(nèi)存大小限制
> node --max-new-space-size=1024 test.js // 單位為KB航瞭,新生代內(nèi)存大小限制
>
> process.memoryUsage() // 查看內(nèi)存使用情況
< {
    rss: 14958592,
    heapTotal: 7195904, // 申請到的堆內(nèi)存
    heapUsed: 2821496   // 使用的量
}
  • V8 內(nèi)存分代:新生代中的對象存活時間較短;老生代中的對象長駐或常駐內(nèi)存坦辟;

    • 老生代在 64 位系統(tǒng)下默認大小限制為 1400 MB刊侯,在 32 位 700 MB
    • 老生代在 32 位系統(tǒng)下默認大小限制為 32 MB,在 32 位 16 MB
  • 回收算法:Scavenge 算法(新生代)锉走、Mark-Sweep & Mark-Compact(老生代兩者結(jié)合使用)

    • Scavenge:將堆內(nèi)存一分為二滨彻,二者只有一個處理使用狀態(tài),稱為 From 空間挪蹭,空閑的為 To 空間亭饵。
      • 分配對象在 From 空間。
      • 垃圾回收時梁厉,釋放 From 中非活對象辜羊,復(fù)制存活對象到 To 空間。
      • 完成復(fù)制后兩個空間的角色發(fā)生對換(又稱翻轉(zhuǎn))词顾。
      • 晉升:當(dāng)一個對象多次復(fù)制后依然存活八秃,則移動到老生代中;或 To 空間使用超過 25%肉盹。
    • Mark-Sweep:標(biāo)記活著的對象昔驱,回收沒有標(biāo)記的對象。(會造成空間不連續(xù))
    • Mark-Compact:將活著的對象往一端移動上忍,移動完成后骤肛,清理掉邊界外的內(nèi)存纳本。
  • 回收執(zhí)行時機

    • 全停頓 stop-the-world:GC 執(zhí)行時需要將應(yīng)用暫停,完成 GC 再恢復(fù)(時間代價較大)
    • 增量標(biāo)記 incremental marking:(老生代)從標(biāo)記階段入手腋颠,拆分為小步饮醇,邏輯執(zhí)行與 GC 交替執(zhí)行
    • 后續(xù)還引入了延遲清理(lazy sweeping)與增量式整理(incremental compaction),讓清理與整理動作也變成增量式的秕豫,進一步利用多核性能。
  • GC 耗時分析:Node 啟動時使用--prof 參數(shù)观蓄,可以得到 V8 執(zhí)行時的性能分析數(shù)據(jù)混移。但得到的日志文件不具備可讀性,需要借助工具(linux-tick-processor侮穿、windows-tick-processor.bat)linux-tick-processor v8.log來分析 GC 的耗時

作用域和閉包對內(nèi)存的影響

  • 形成作用域:函數(shù)調(diào)用歌径、with、全局作用域

    • 局部變量分配在作用域空間上亲茅,隨作用域回收而釋放回铛。如果變量小周期短會被分配到新生代的 Form 空間。
    • 作用域鏈:在當(dāng)前作用域中無法找到變量的聲明克锣,將會向上級的作用域里查找茵肃,直到查到為止。
    • 變量的主動釋放:=undefined 或 delete 對象袭祟。在 node10 中验残,對象和計算量多的情況下,執(zhí)行 =undefined 比 delete 大部分時間是快的巾乳,在 chrome90 中更明顯您没。
  • 閉包:外部作用域訪問內(nèi)部作用域中變量的方法叫做閉包(closure)

    • 比如 A 方法返回 B 函數(shù),B 訪問 A 中變量胆绊,a = A()氨鹏,只要 a 不被釋放 B 占用的內(nèi)存就得不到釋放。(a 不使用了及時=undefined压状,以釋放 B)

查看內(nèi)存使用情況

> process.memoryUsage() // 查看內(nèi)存使用情況
< {
    rss: 14958592,      // 常駐集大小仆抵,包括所有 C++ 和 JavaScript 對象和代碼;
    heapTotal: 7195904, // 申請到的堆內(nèi)存
    heapUsed: 2821496,  // 使用的量
    external: 13522,    // 綁定到 V8 管理的 JavaScript 對象的 C++ 對象的內(nèi)存使用量何缓。
    arrayBuffers: 15159 // 所有 Node.js Buffer
}
> os.totalmem() // 系統(tǒng)的總內(nèi)存
< 8589934592
> os.freemem()  // 系統(tǒng)閑置內(nèi)存
< 185921536
  • rss: resident set size 常駐集大小
  • external:外部的肢础,堆外內(nèi)存

heapTotal 不包含 rss、arrayBuffers碌廓、external传轰。external 包含 arrayBuffers。批量操作 buffer 后谷婆,heapTotal慨蛙、heapUsed 不變辽聊,arrayBuffers、external 變大(rss 短時漲一些期贫,很快就下去了)

內(nèi)存泄漏

  • 常見原因:意外的全局變量跟匆、沒有及時清理的計時器或回調(diào)、閉包

  • 慎將內(nèi)存當(dāng)做緩存通砍,比如 store 中的大對象玛臂、已經(jīng)不用的變量,導(dǎo)致內(nèi)存泄漏甚至溢出封孙。存儲需求強烈可以使用 Redis 或 indexdb 等不占用 v8 緩存限制的方式迹冤。

  • 日志收集時,寫入操作慢于日志產(chǎn)生虎忌,js 隊列數(shù)據(jù)過大導(dǎo)致內(nèi)存溢出泡徙,或記錄日志的 js 相關(guān)作用域得不到釋放,出現(xiàn)內(nèi)存泄漏膜蠢。

內(nèi)存泄漏排查工具

  • v8-profiler堪藐,可以用來分析 cpu(書中未詳說)

  • node-heapdump:npm i heapdump;在代碼的第一行添加如下代碼將其引入挑围;kill -USR2 PID生成分析文件礁竞;導(dǎo)入 chrome 中的 Profiles 中進行分析,根據(jù)新生代(shallow size)杉辙、老生代(retained size)內(nèi)存占比推測泄漏的數(shù)據(jù)

  • node-memwatch:npm i memwatch苏章;通過改變 heapDiff 的開始位置,或許可以逐步定位泄漏的位置奏瞬,通過 diff 結(jié)果可以推測泄漏的為數(shù)組

    memwatch.on("stats", cb)  // 全堆垃圾回收
    memwatch.on("leak", cb)  // 內(nèi)存泄漏(如連續(xù)5次垃圾回收內(nèi)存仍未釋放)
    var hd = new memwatch.HeapDiff();
    /* 要分析的代碼 */
    ...
    
    var diff = hd.end(); // 內(nèi)容差異結(jié)果
    /*
    {
      what: "String",
      size_bytes: 879424,
      size: "858.81 kb",
      +: 20001,   // 分配的字符串對象數(shù)量
      -: 1        // 釋放的字符串對象數(shù)量
    }
    */
    
  • 流(stream)或管道(pipe)專門用來操作需要大內(nèi)存的數(shù)據(jù)枫绅,且不受 V8 內(nèi)存限制的影響

  • 純粹的 Buffer 操作(不涉及字符串),也不受 V8 內(nèi)存限制的影響

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末硼端,一起剝皮案震驚了整個濱河市并淋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌珍昨,老刑警劉巖县耽,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異镣典,居然都是意外死亡兔毙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門兄春,熙熙樓的掌柜王于貴愁眉苦臉地迎上來澎剥,“玉大人,你說我怎么就攤上這事赶舆⊙埔Γ” “怎么了祭饭?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長叙量。 經(jīng)常有香客問我倡蝙,道長,這世上最難降的妖魔是什么绞佩? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任寺鸥,我火速辦了婚禮,結(jié)果婚禮上品山,老公的妹妹穿的比我還像新娘析既。我一直安慰自己,他們只是感情好谆奥,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拂玻,像睡著了一般酸些。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上檐蚜,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天魄懂,我揣著相機與錄音,去河邊找鬼闯第。 笑死市栗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的咳短。 我是一名探鬼主播填帽,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼咙好!你這毒婦竟也來了篡腌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤勾效,失蹤者是張志新(化名)和其女友劉穎嘹悼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體层宫,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡杨伙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了萌腿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片限匣。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖毁菱,靈堂內(nèi)的尸體忽然破棺而出膛腐,到底是詐尸還是另有隱情睛约,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布哲身,位于F島的核電站辩涝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏勘天。R本人自食惡果不足惜怔揩,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脯丝。 院中可真熱鬧商膊,春花似錦、人聲如沸宠进。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽材蹬。三九已至实幕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堤器,已是汗流浹背昆庇。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留闸溃,地道東北人整吆。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像辉川,于是被迫代替她去往敵國和親表蝙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容