国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

目錄
一、為什么需要GC
二、GC發(fā)展
三、v8 內(nèi)存分區(qū)與GC
3.1 分代策略:新生代和老生代
3.1.1 新生代
3.1.2 老生代
3.2 大對(duì)象空間 large object space
四、V8 新老分區(qū)大小
4.1 老生代分區(qū)大小
4.2 新生代分區(qū)大小
五、 內(nèi)存分析相關(guān)API
5.1 v8.getHeapStatistics()
5.2 process.memoryUsage
5.3 開啟打印GC事件
5.4 內(nèi)存快照
六、利用內(nèi)存快照分析內(nèi)存泄漏
泄漏解決和優(yōu)化
六、 最后
首頁(yè) web前端 js教程 圖文詳解Node V8引擎的內(nèi)存和GC

圖文詳解Node V8引擎的內(nèi)存和GC

Mar 29, 2023 pm 06:02 PM
node.js 后端 v8

本篇文章帶大家深入了解NodeJS V8引擎的內(nèi)存和垃圾回收器(GC),希望對(duì)大家有所幫助!

圖文詳解Node V8引擎的內(nèi)存和GC

一、為什么需要GC

程序應(yīng)用運(yùn)行需要使用內(nèi)存,其中內(nèi)存的兩個(gè)分區(qū)是我們常常會(huì)討論的概念:棧區(qū)和堆區(qū)。

棧區(qū)是線性的隊(duì)列,隨著函數(shù)運(yùn)行結(jié)束自動(dòng)釋放的,而堆區(qū)是自由的動(dòng)態(tài)內(nèi)存空間、堆內(nèi)存是手動(dòng)分配釋放或者 垃圾回收程序(Garbage Collection,后文都簡(jiǎn)稱GC)自動(dòng)分配釋放的。

軟件發(fā)展早期或者一些語(yǔ)言對(duì)于堆內(nèi)存都是手動(dòng)操作分配和釋放,比如 C、C++。雖然能精準(zhǔn)操作內(nèi)存,達(dá)到盡可能的最優(yōu)內(nèi)存使用,但是開發(fā)效率卻非常低,也容易出現(xiàn)內(nèi)存操作不當(dāng)。【相關(guān)教程推薦:nodejs視頻教程、編程教學(xué)

隨著技術(shù)發(fā)展,高級(jí)語(yǔ)言(例如Java Node)都不需要開發(fā)者手動(dòng)操作內(nèi)存,程序語(yǔ)言自動(dòng)會(huì)分配和釋放空間。同時(shí)也誕生了 GC(Garbage Collection)垃圾回收器,幫助釋放和整理內(nèi)存。開發(fā)者大部分情況不需要關(guān)心內(nèi)存本身,可以專注業(yè)務(wù)開發(fā)。后文主要是討論堆內(nèi)存和 GC。

二、GC發(fā)展

GC運(yùn)行會(huì)消耗CPU資源,GC運(yùn)行的過(guò)程會(huì)觸發(fā)STW(stop-the-world)暫停業(yè)務(wù)代碼線程,為什么會(huì) STW 呢?是為了保證在 GC 的過(guò)程中,不會(huì)和新創(chuàng)建的對(duì)象起沖突。

GC主要是伴隨內(nèi)存大小增加而發(fā)展演化。大致分為3個(gè)大的代表性階段:

  • 階段一 單線程GC(代表:serial)

單線程GC,在它進(jìn)行垃圾收集時(shí),必須完全暫停其他所有的工作線程 ,它是最初階段的GC,性能也是最差的

  • 階段二 并行多線程GC(代表:Parallel Scavenge, ParNew)

在多 CPU 環(huán)境中利用多條 GC 線程同時(shí)并行運(yùn)行,從而垃圾回收的時(shí)間減少、用戶線程停頓的時(shí)間也減少,這個(gè)算法也會(huì)STW,完全暫停其他所有的工作線程

  • 階段三 多線程并發(fā) concurrent GC(代表:CMS (Concurrent Mark Sweep) G1)

這里的并發(fā)是指:GC多線程執(zhí)行可以和業(yè)務(wù)代碼并發(fā)運(yùn)行。

在前面的兩個(gè)發(fā)展階段的 GC 算法都會(huì)完全 STW,而在 concurrent GC 中,有部分階段 GC 線程可以和業(yè)務(wù)代碼并發(fā)運(yùn)行,保證了更短的 STW 時(shí)間。但是這個(gè)模式就會(huì)存在標(biāo)記錯(cuò)誤,因?yàn)?GC 過(guò)程中可能有新對(duì)象進(jìn)來(lái),當(dāng)然算法本身會(huì)修正和解決這個(gè)問(wèn)題

上面的三個(gè)階段并不代表 GC 一定是上面描述三種的其中一種。不同程序語(yǔ)言的 GC 根據(jù)不同需求采用多種算法組合實(shí)現(xiàn)。

三、v8 內(nèi)存分區(qū)與GC

堆內(nèi)存設(shè)計(jì)與GC設(shè)計(jì)是緊密相關(guān)的。V8 把堆內(nèi)存分為幾大區(qū)域,采用分代策略。

盜圖:

image.png

  • 新生代(new-space 或 young-generation):空間小,分為了兩個(gè)半空間(semi-space),其中的數(shù)據(jù)存活期短。
  • 老生代(old-space 或 old-generation):空間大,可增量,其中的數(shù)據(jù)存活期長(zhǎng)
  • 大對(duì)象空間(large-object-space):默認(rèn)超過(guò)256K的對(duì)象會(huì)在此空間下,下文解釋
  • 代碼空間(code-space):即時(shí)編譯器(JIT)在這里存儲(chǔ)已編譯的代碼
  • 元空間 (cell space):這個(gè)空間用于存儲(chǔ)小的、固定大小的JavaScript對(duì)象,比如數(shù)字和布爾值。
  • 屬性元空間 (property cell space):這個(gè)空間用于存儲(chǔ)特殊的JavaScript對(duì)象,比如訪問(wèn)器屬性和某些內(nèi)部對(duì)象。
  • Map Space:這個(gè)空間用于存儲(chǔ)用于JavaScript對(duì)象的元信息和其他內(nèi)部數(shù)據(jù)結(jié)構(gòu),比如Map和Set對(duì)象。

3.1 分代策略:新生代和老生代

新老生代.png

在 Node.js 中,GC 采用分代策略,分為新、老生代區(qū),內(nèi)存數(shù)據(jù)大都在這兩個(gè)區(qū)域。

3.1.1 新生代

新生代是一個(gè)小的、存儲(chǔ)年齡小的對(duì)象、快速的內(nèi)存池,分為了兩個(gè)半空間(semi-space),一半的空間是空閑的(稱為to空間),另一半的空間是存儲(chǔ)了數(shù)據(jù)(稱為from空間)。

當(dāng)對(duì)象首次創(chuàng)建時(shí),它們被分配到新生代 from 半空間中,它的年齡為1。當(dāng) from 空間不足或者超過(guò)一定大小數(shù)量之后,會(huì)觸發(fā) Minor GC(采用復(fù)制算法 Scavenge),此時(shí),GC 會(huì)暫停應(yīng)用程序的執(zhí)行(STW,stop-the-world),標(biāo)記(from空間)中所有活動(dòng)對(duì)象,然后將它們整理連續(xù)移動(dòng)到新生代的另一個(gè)空閑空間(to空間)中。最后原本的 from 空間的內(nèi)存會(huì)被全部釋放而變成空閑空間,兩個(gè)空間就完成 fromto 的對(duì)換,復(fù)制算法是犧牲了空間換取時(shí)間的算法。

新生代的空間更小,所以此空間會(huì)更頻繁的觸發(fā) GC。同時(shí)掃描的空間更小,GC性能消耗也更小、它的 GC 執(zhí)行時(shí)間也更短。

每當(dāng)一次 Minor GC 完成存活的對(duì)象年齡就+1,經(jīng)歷過(guò)多次Minor GC還存活的對(duì)象(年齡大于N),它們將被移動(dòng)到老生代內(nèi)存池中。

3.1.2 老生代

老生代是一個(gè)大的內(nèi)存池,用于存儲(chǔ)較長(zhǎng)壽命的對(duì)象。老生代內(nèi)存采用 標(biāo)記清除(Mark-Sweep)標(biāo)記壓縮算法(Mark-Compact)。它的一次執(zhí)行叫做 Mayor GC。當(dāng)老生代中的對(duì)象占滿一定比例時(shí),即存活對(duì)象與總對(duì)象的比例超過(guò)一定的閾值,就會(huì)觸發(fā)一次 標(biāo)記清除標(biāo)記壓縮

因?yàn)樗目臻g更大,它的GC執(zhí)行時(shí)間也更長(zhǎng),頻率相對(duì)新生代更低。如果老生代完成 GC 回收之后空間還是不足,V8 就會(huì)從系統(tǒng)中申請(qǐng)更多內(nèi)存。

可以手動(dòng)執(zhí)行 global.gc() 方法,設(shè)置不同參數(shù),主動(dòng)觸發(fā)GC。 但是需要注意的是,默認(rèn)情況下,Node.js 是禁用了此方法。如果要啟用,可以通過(guò)啟動(dòng) Node.js 應(yīng)用程序時(shí)添加 --expose-gc 參數(shù)來(lái)開啟,例如:

node --expose-gc app.js

V8 在老生代中主要采用了 Mark-SweepMark-Compact 相結(jié)合的方式進(jìn)行垃圾回收。

Mark-Sweep 是標(biāo)記清除的意思,它分為兩個(gè)階段,標(biāo)記和清除。Mark-Sweep 在標(biāo)記階段遍歷堆中的所有對(duì)象,并標(biāo)記活著的對(duì)象,在隨后的清除階段中,只清除未被標(biāo)記的對(duì)象。

Mark-Sweep 最大的問(wèn)題是在進(jìn)行一次標(biāo)記清除回收后,內(nèi)存空間會(huì)出現(xiàn)不連續(xù)的狀態(tài)。這種內(nèi)存碎片會(huì)對(duì)后續(xù)的內(nèi)存分配造成問(wèn)題,因?yàn)楹芸赡艹霈F(xiàn)需要分配一個(gè)大對(duì)象的情況,這時(shí)所有的碎片空間都無(wú)法完成此次分配,就會(huì)提前觸發(fā)垃圾回收,而這次回收是不必要的。

為了解決 Mark-Sweep 的內(nèi)存碎片問(wèn)題,Mark-Compact 被提出來(lái)。Mark-Compact 是標(biāo)記整理的意思,是在 Mark-Sweep 的基礎(chǔ)上演進(jìn)而來(lái)的。它們的差別在于對(duì)象在標(biāo)記為死亡后,在整理過(guò)程中,將活著的對(duì)象往一端移動(dòng),移動(dòng)完成后,直接清理掉邊界外的內(nèi)存。V8 也會(huì)根據(jù)一定邏輯,釋放一定空閑的內(nèi)存還給系統(tǒng)。

3.2 大對(duì)象空間 large object space

大對(duì)象會(huì)直接在大對(duì)象空間創(chuàng)建,并且不會(huì)移動(dòng)到其它空間。那么到底多大的對(duì)象會(huì)直接在大對(duì)象空間創(chuàng)建,而不是在新生代 from 區(qū)中創(chuàng)建呢?查閱資料和源代碼終于找到了答案。默認(rèn)情況下是 256KV8 似乎并沒(méi)有暴露修改命令,源碼中的 v8_enable_hugepage 配置應(yīng)該是打包的時(shí)候設(shè)定的。

chromium.googlesource.com/v8/v8.git/+…

 // There is a separate large object space for objects larger than
 // Page::kMaxRegularHeapObjectSize, so that they do not have to move during
 // collection. The large object space is paged. Pages in large object space
 // may be larger than the page size.

source.chromium.org/chromium/ch…

1.png

image.png

(1 << (18 - 1)) 的結(jié)果 256K
(1 << (19 - 1)) 的結(jié)果 256K
(1 << (21 - 1)) 的結(jié)果 1M(如果開啟了hugPage)

四、V8 新老分區(qū)大小

4.1 老生代分區(qū)大小

在v12.x 之前:

為了保證 GC 的執(zhí)行時(shí)間保持在一定范圍內(nèi),V8 限制了最大內(nèi)存空間,設(shè)置了一個(gè)默認(rèn)老生代內(nèi)存最大值,64位系統(tǒng)中為大約1.4G,32位為大約700M,超出會(huì)導(dǎo)致應(yīng)用崩潰。

如果想加大內(nèi)存,可以使用 --max-old-space-size 設(shè)置最大內(nèi)存(單位:MB)

node --max_old_space_size=

在v12以后:

V8 將根據(jù)可用內(nèi)存分配老生代大小,也可以說(shuō)是堆內(nèi)存大小,所以并沒(méi)有限制堆內(nèi)存大小。以前的限制邏輯,其實(shí)不合理,限制了 V8 的能力,總不能因?yàn)?GC 過(guò)程消耗的時(shí)間更長(zhǎng),就不讓我繼續(xù)運(yùn)行程序吧,后續(xù)的版本也對(duì) GC 做了更多優(yōu)化,內(nèi)存越來(lái)越大也是發(fā)展需要。

如果想要做限制,依然可以使用 --max-old-space-size 配置, v12 以后它的默認(rèn)值是0,代表不限制。

參考文檔:nodejs.medium.com/introducing…

4.2 新生代分區(qū)大小

新生代中的一個(gè) semi-space 大小 64位系統(tǒng)的默認(rèn)值是16M,32位系統(tǒng)是8M,因?yàn)橛?個(gè) semi-space,所以總大小是32M、16M。

--max-semi-space-size

--max-semi-space-size 設(shè)置新生代 semi-space 最大值,單位為MB。

此空間不是越大越好,空間越大掃描的時(shí)間就越長(zhǎng)。這個(gè)分區(qū)大部分情況下是不需要做修改的,除非針對(duì)具體的業(yè)務(wù)場(chǎng)景做優(yōu)化,謹(jǐn)慎使用。

--max-new-space-size

--max-new-space-size 設(shè)置新生代空間最大值,單位為KB(不存在)

有很多文章說(shuō)到此功能,我翻了下 nodejs.org 網(wǎng)頁(yè)中 v4 v6 v7 v8 v10的文檔都沒(méi)有看到有這個(gè)配置,使用 node --v8-options 也沒(méi)有查到,也許以前的某些老版本有,而現(xiàn)在都應(yīng)該使用 --max-semi-space-size。

五、 內(nèi)存分析相關(guān)API

5.1 v8.getHeapStatistics()

執(zhí)行 v8.getHeapStatistics(),查看 v8 堆內(nèi)存信息,查詢最大堆內(nèi)存 heap_size_limit,當(dāng)然這里包含了新、老生代、大對(duì)象空間等。我的電腦硬件內(nèi)存是 8G,Node版本16x,查看到 heap_size_limit 是4G。

{
  total_heap_size: 6799360,
  total_heap_size_executable: 524288,
  total_physical_size: 5523584,
  total_available_size: 4340165392,
  used_heap_size: 4877928,
  heap_size_limit: 4345298944,
  malloced_memory: 254120,
  peak_malloced_memory: 585824,
  does_zap_garbage: 0,
  number_of_native_contexts: 2,
  number_of_detached_contexts: 0
}

k8s 容器中查詢 NodeJs 應(yīng)用,分別查看了v12 v14 v16版本,如下表??雌饋?lái)是本身系統(tǒng)當(dāng)前的最大內(nèi)存的一半。128M 的時(shí)候,為啥是 256M,因?yàn)槿萜髦羞€有交換內(nèi)存,容器內(nèi)存實(shí)際最大內(nèi)存限制是內(nèi)存限制值 x2,有同等的交換內(nèi)存。

所以結(jié)論是大部分情況下 heap_size_limit 的默認(rèn)值是系統(tǒng)內(nèi)存的一半。但是如果超過(guò)這個(gè)值且系統(tǒng)空間足夠,V8 還是會(huì)申請(qǐng)更多空間。當(dāng)然這個(gè)結(jié)論也不是一個(gè)最準(zhǔn)確的結(jié)論。而且隨著內(nèi)存使用的增多,如果系統(tǒng)內(nèi)存還足夠,這里的最大內(nèi)存還會(huì)增長(zhǎng)。

容器最大內(nèi)存heap_size_limit
4G2G
2G1G
1G0.5G
1.5G0.7G
256M256M
128M256M

5.2 process.memoryUsage

process.memoryUsage()
{
  rss: 35438592,
  heapTotal: 6799360,
  heapUsed: 4892976,
  external: 939130,
  arrayBuffers: 11170
}

通過(guò)它可以查看當(dāng)前進(jìn)程的內(nèi)存占用和使用情況 heapTotal、heapUsed,可以定時(shí)獲取此接口,然后繪畫出折線圖幫助分析內(nèi)存占用情況。以下是 Easy-Monitor 提供的功能:

image.png

建議本地開發(fā)環(huán)境使用,開啟后,嘗試大量請(qǐng)求,會(huì)看到內(nèi)存曲線增長(zhǎng),到請(qǐng)求結(jié)束之后,GC觸發(fā)后會(huì)看到內(nèi)存曲線下降,然后再嘗試多次發(fā)送大量請(qǐng)求,這樣往復(fù)下來(lái),如果發(fā)現(xiàn)內(nèi)存一直在增長(zhǎng)低谷值越來(lái)越高,就可能是發(fā)生了內(nèi)存泄漏。

5.3 開啟打印GC事件

使用方法

node --trace_gc app.js
// 或者
v8.setFlagsFromString(&#39;--trace_gc&#39;);
  • --trace_gc
[40807:0x148008000]   235490 ms: Scavenge 247.5 (259.5) -> 244.7 (260.0) MB, 0.8 / 0.0 ms  (average mu = 0.971, current mu = 0.908) task 
[40807:0x148008000]   235521 ms: Scavenge 248.2 (260.0) -> 245.2 (268.0) MB, 1.2 / 0.0 ms  (average mu = 0.971, current mu = 0.908) allocation failure 
[40807:0x148008000]   235616 ms: Scavenge 251.5 (268.0) -> 245.9 (268.8) MB, 1.9 / 0.0 ms  (average mu = 0.971, current mu = 0.908) task 
[40807:0x148008000]   235681 ms: Mark-sweep 249.7 (268.8) -> 232.4 (268.0) MB, 7.1 / 0.0 ms  (+ 46.7 ms in 170 steps since start of marking, biggest step 4.2 ms, walltime since start of marking 159 ms) (average mu = 1.000, current mu = 1.000) finalize incremental marking via task GC in old space requested
GCType <heapUsed before> (<heapTotal before>) -> <heapUsed after> (<heapTotal after>) MB

上面的 ScavengeMark-sweep 代表GC類型,Scavenge 是新生代中的清除事件,Mark-sweep 是老生代中的標(biāo)記清除事件。箭頭符號(hào)前是事件發(fā)生前的實(shí)際使用內(nèi)存大小,箭頭符號(hào)后是事件結(jié)束后的實(shí)際使用內(nèi)存大小,括號(hào)內(nèi)是內(nèi)存空間總值??梢钥吹叫律惺录l(fā)生的頻率很高,而后觸發(fā)的老生代事件會(huì)釋放總內(nèi)存空間。

  • --trace_gc_verbose

展示堆空間的詳細(xì)情況

v8.setFlagsFromString(&#39;--trace_gc_verbose&#39;);

[44729:0x130008000] Fast promotion mode: false survival rate: 19%
[44729:0x130008000]    97120 ms: [HeapController] factor 1.1 based on mu=0.970, speed_ratio=1000 (gc=433889, mutator=434)
[44729:0x130008000]    97120 ms: [HeapController] Limit: old size: 296701 KB, new limit: 342482 KB (1.1)
[44729:0x130008000]    97120 ms: [GlobalMemoryController] Limit: old size: 296701 KB, new limit: 342482 KB (1.1)
[44729:0x130008000]    97120 ms: Scavenge 302.3 (329.9) -> 290.2 (330.4) MB, 8.4 / 0.0 ms  (average mu = 0.998, current mu = 0.999) task 
[44729:0x130008000] Memory allocator,       used: 338288 KB, available: 3905168 KB
[44729:0x130008000] Read-only space,        used:    166 KB, available:      0 KB, committed:    176 KB
[44729:0x130008000] New space,              used:    444 KB, available:  15666 KB, committed:  32768 KB
[44729:0x130008000] New large object space, used:      0 KB, available:  16110 KB, committed:      0 KB
[44729:0x130008000] Old space,              used: 253556 KB, available:   1129 KB, committed: 259232 KB
[44729:0x130008000] Code space,             used:  10376 KB, available:    119 KB, committed:  12944 KB
[44729:0x130008000] Map space,              used:   2780 KB, available:      0 KB, committed:   2832 KB
[44729:0x130008000] Large object space,     used:  29987 KB, available:      0 KB, committed:  30336 KB
[44729:0x130008000] Code large object space,     used:      0 KB, available:      0 KB, committed:      0 KB
[44729:0x130008000] All spaces,             used: 297312 KB, available: 3938193 KB, committed: 338288 KB
[44729:0x130008000] Unmapper buffering 0 chunks of committed:      0 KB
[44729:0x130008000] External memory reported:  20440 KB
[44729:0x130008000] Backing store memory:  22084 KB
[44729:0x130008000] External memory global 0 KB
[44729:0x130008000] Total time spent in GC  : 199.1 ms
  • --trace_gc_nvp

每次GC事件的詳細(xì)信息,GC類型,各種時(shí)間消耗,內(nèi)存變化等

v8.setFlagsFromString(&#39;--trace_gc_nvp&#39;);

[45469:0x150008000]  8918123 ms: pause=0.4 mutator=83.3 gc=s reduce_memory=0 time_to_safepoint=0.00 heap.prologue=0.00 heap.epilogue=0.00 heap.epilogue.reduce_new_space=0.00 heap.external.prologue=0.00 heap.external.epilogue=0.00 heap.external_weak_global_handles=0.00 fast_promote=0.00 complete.sweep_array_buffers=0.00 scavenge=0.38 scavenge.free_remembered_set=0.00 scavenge.roots=0.00 scavenge.weak=0.00 scavenge.weak_global_handles.identify=0.00 scavenge.weak_global_handles.process=0.00 scavenge.parallel=0.08 scavenge.update_refs=0.00 scavenge.sweep_array_buffers=0.00 background.scavenge.parallel=0.00 background.unmapper=0.04 unmapper=0.00 incremental.steps_count=0 incremental.steps_took=0.0 scavenge_throughput=1752382 total_size_before=261011920 total_size_after=260180920 holes_size_before=838480 holes_size_after=838480 allocated=831000 promoted=0 semi_space_copied=4136 nodes_died_in_new=0 nodes_copied_in_new=0 nodes_promoted=0 promotion_ratio=0.0% average_survival_ratio=0.5% promotion_rate=0.0% semi_space_copy_rate=0.5% new_space_allocation_throughput=887.4 unmapper_chunks=124
[45469:0x150008000]  8918234 ms: pause=0.6 mutator=110.9 gc=s reduce_memory=0 time_to_safepoint=0.00 heap.prologue=0.00 heap.epilogue=0.00 heap.epilogue.reduce_new_space=0.04 heap.external.prologue=0.00 heap.external.epilogue=0.00 heap.external_weak_global_handles=0.00 fast_promote=0.00 complete.sweep_array_buffers=0.00 scavenge=0.50 scavenge.free_remembered_set=0.00 scavenge.roots=0.08 scavenge.weak=0.00 scavenge.weak_global_handles.identify=0.00 scavenge.weak_global_handles.process=0.00 scavenge.parallel=0.08 scavenge.update_refs=0.00 scavenge.sweep_array_buffers=0.00 background.scavenge.parallel=0.00 background.unmapper=0.04 unmapper=0.00 incremental.steps_count=0 incremental.steps_took=0.0 scavenge_throughput=1766409 total_size_before=261207856 total_size_after=260209776 holes_size_before=838480 holes_size_after=838480 allocated=1026936 promoted=0 semi_space_copied=3008 nodes_died_in_new=0 nodes_copied_in_new=0 nodes_promoted=0 promotion_ratio=0.0% average_survival_ratio=0.5% promotion_rate=0.0% semi_space_copy_rate=0.3% new_space_allocation_throughput=888.1 unmapper_chunks=124

5.4 內(nèi)存快照

const { writeHeapSnapshot } = require(&#39;node:v8&#39;);
v8.writeHeapSnapshot()

打印快照,將會(huì)STW,服務(wù)停止響應(yīng),內(nèi)存占用越大,時(shí)間越長(zhǎng)。此方法本身就比較費(fèi)時(shí)間,所以生成的過(guò)程預(yù)期不要太高,耐心等待。

注意:生成內(nèi)存快照的過(guò)程,會(huì)STW(程序?qū)和#缀鯚o(wú)任何響應(yīng),如果容器使用了健康檢測(cè),這時(shí)無(wú)法響應(yīng)的話,容器可能被重啟,導(dǎo)致無(wú)法獲取快照,如果需要生成快照、建議先關(guān)閉健康檢測(cè)。

兼容性問(wèn)題:此 API arm64 架構(gòu)不支持,執(zhí)行就會(huì)卡住進(jìn)程 生成空快照文件 再無(wú)響應(yīng), 如果使用庫(kù) heapdump,會(huì)直接報(bào)錯(cuò):

(mach-o file, but is an incompatible architecture (have (arm64), need (x86_64))

API 會(huì)生成一個(gè) .heapsnapshot 后綴快照文件,可以使用 Chrome 調(diào)試器的“內(nèi)存”功能,導(dǎo)入快照文件,查看堆內(nèi)存具體的對(duì)象數(shù)和大小,以及到GC根結(jié)點(diǎn)的距離等。也可以對(duì)比兩個(gè)不同時(shí)間快照文件的區(qū)別,可以看到它們之間的數(shù)據(jù)量變化。

六、利用內(nèi)存快照分析內(nèi)存泄漏

一個(gè) Node 應(yīng)用因?yàn)閮?nèi)存超過(guò)容器限制經(jīng)常發(fā)生重啟,通過(guò)容器監(jiān)控后臺(tái)看到應(yīng)用內(nèi)存的曲線是一直上升的,那應(yīng)該是發(fā)生了內(nèi)存泄漏。

使用 Chrome 調(diào)試器對(duì)比了不同時(shí)間的快照。發(fā)現(xiàn)對(duì)象增量最多的是閉包函數(shù),繼而展開查看整個(gè)列表,發(fā)現(xiàn)數(shù)據(jù)量較多的是 mongo 文檔對(duì)象,其實(shí)就是閉包函數(shù)內(nèi)的數(shù)據(jù)沒(méi)有被釋放,再通過(guò)查看 Object 列表,發(fā)現(xiàn)同樣很多對(duì)象,最外層的詳情顯示的是 MongooseConnection 對(duì)象。

image.png

image.png

到此為止,已經(jīng)大概定位到一個(gè)類的 mongo 數(shù)據(jù)存儲(chǔ)邏輯附近有內(nèi)存泄漏。

再看到 Timeout 對(duì)象也比較多,從 GC 根節(jié)點(diǎn)距離來(lái)看,這些對(duì)象距離非常深。點(diǎn)開詳情,看到這一層層的嵌套就定位到了代碼中準(zhǔn)確的位置。因?yàn)槟莻€(gè)類中有個(gè)定時(shí)任務(wù)使用 setInterval 定時(shí)器去分批處理一些不緊急任務(wù),當(dāng)一個(gè) setInterval 把事情做完之后就會(huì)被 clearInterval 清除。

image.pngimage.png

泄漏解決和優(yōu)化

通過(guò)代碼邏輯分析,最終找到了問(wèn)題所在,是 clearInterval 的觸發(fā)條件有問(wèn)題,導(dǎo)致定時(shí)器沒(méi)有被清除一直循環(huán)下去。定時(shí)器一直執(zhí)行,這段代碼和其中的數(shù)據(jù)還在閉包之中,無(wú)法被 GC 回收,所以內(nèi)存會(huì)越來(lái)越大直至達(dá)到上限崩潰。

這里使用 setInterval 的方式并不合理,順便改成了利用 for await 隊(duì)列順序執(zhí)行,從而達(dá)到避免同時(shí)間大量并發(fā)的效果,代碼也要清晰許多。由于這塊代碼比較久遠(yuǎn),就不考慮為啥當(dāng)初使用 setInterval 了。

發(fā)布新版本之后,觀察了十多天,內(nèi)存平均保持在100M出頭,GC 正?;厥张R時(shí)增長(zhǎng)的內(nèi)存,呈現(xiàn)為波浪曲線,沒(méi)有再出現(xiàn)泄漏。

image.png

至此利用內(nèi)存快照,分析并解決了內(nèi)存泄漏。當(dāng)然實(shí)際分析的時(shí)候要曲折一點(diǎn),這個(gè)內(nèi)存快照的內(nèi)容并不好理解、并不那么直接??煺諗?shù)據(jù)的展示是類型聚合的,需要通過(guò)看不同的構(gòu)造函數(shù),以及內(nèi)部的數(shù)據(jù)詳情,結(jié)合自己的代碼綜合分析,才能找到一些線索。 比如從當(dāng)時(shí)我得到的內(nèi)存快照看,有大量數(shù)據(jù)是 閉包、string、mongo model類、Timeout、Object等,其實(shí)這些增量的數(shù)據(jù)都是來(lái)自于那段有問(wèn)題的代碼,并且無(wú)法被 GC 回收。

六、 最后

不同的語(yǔ)言 GC 實(shí)現(xiàn)都不一樣,比如 JavaGo

Java:了解 JVM (對(duì)應(yīng)Node V8)的知道,Java 也采用分代策略,它的新生代中還存在一個(gè) eden 區(qū),新生的對(duì)象都在這個(gè)區(qū)域創(chuàng)建。而 V8 新生代沒(méi)有 eden 區(qū)。

Go:采用標(biāo)記清除,三色標(biāo)記算法

不同的語(yǔ)言的 GC 實(shí)現(xiàn)不同,但是本質(zhì)上都是采用不同算法組合實(shí)現(xiàn)。在性能上,不同的組合,帶來(lái)的各方面性能效率不一樣,但都是此消彼長(zhǎng),只是偏向不同的應(yīng)用場(chǎng)景而已。

更多node相關(guān)知識(shí),請(qǐng)?jiān)L問(wèn):nodejs 教程

以上是圖文詳解Node V8引擎的內(nèi)存和GC的詳細(xì)內(nèi)容。更多信息請(qǐng)關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

本站聲明
本文內(nèi)容由網(wǎng)友自發(fā)貢獻(xiàn),版權(quán)歸原作者所有,本站不承擔(dān)相應(yīng)法律責(zé)任。如您發(fā)現(xiàn)有涉嫌抄襲侵權(quán)的內(nèi)容,請(qǐng)聯(lián)系admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費(fèi)脫衣服圖片

Undresser.AI Undress

Undresser.AI Undress

人工智能驅(qū)動(dòng)的應(yīng)用程序,用于創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用于從照片中去除衣服的在線人工智能工具。

Clothoff.io

Clothoff.io

AI脫衣機(jī)

Video Face Swap

Video Face Swap

使用我們完全免費(fèi)的人工智能換臉工具輕松在任何視頻中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費(fèi)的代碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

功能強(qiáng)大的PHP集成開發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺(jué)化網(wǎng)頁(yè)開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級(jí)代碼編輯軟件(SublimeText3)

圖文詳解Node V8引擎的內(nèi)存和GC 圖文詳解Node V8引擎的內(nèi)存和GC Mar 29, 2023 pm 06:02 PM

本篇文章帶大家深入了解NodeJS V8引擎的內(nèi)存和垃圾回收器(GC),希望對(duì)大家有所幫助!

一文聊聊Node中的內(nèi)存控制 一文聊聊Node中的內(nèi)存控制 Apr 26, 2023 pm 05:37 PM

基于無(wú)阻塞、事件驅(qū)動(dòng)建立的Node服務(wù),具有內(nèi)存消耗低的優(yōu)點(diǎn),非常適合處理海量的網(wǎng)絡(luò)請(qǐng)求。在海量請(qǐng)求的前提下,就需要考慮“內(nèi)存控制”的相關(guān)問(wèn)題了。 1. V8的垃圾回收機(jī)制與內(nèi)存限制 Js由垃圾回收機(jī)

一起聊聊Node中的事件循環(huán) 一起聊聊Node中的事件循環(huán) Apr 11, 2023 pm 07:08 PM

事件循環(huán)是 Node.js 的基本組成部分,通過(guò)確保主線程不被阻塞來(lái)實(shí)現(xiàn)異步編程,了解事件循環(huán)對(duì)構(gòu)建高效應(yīng)用程序至關(guān)重要。下面本篇文章就來(lái)帶大家深入了解Node中的事件循環(huán) ,希望對(duì)大家有所幫助!

深入淺析Go語(yǔ)言中要有GMP調(diào)度模型的原因 深入淺析Go語(yǔ)言中要有GMP調(diào)度模型的原因 Apr 14, 2023 pm 03:26 PM

Go為什么要有GMP調(diào)度模型?下面本篇文章給大家介紹一下Go語(yǔ)言中要有GMP調(diào)度模型的原因,希望對(duì)大家有所幫助!

分享接口設(shè)計(jì)文檔的12個(gè)注意點(diǎn) 分享接口設(shè)計(jì)文檔的12個(gè)注意點(diǎn) Apr 24, 2023 am 10:58 AM

最近在做接口文檔評(píng)審的時(shí)候,發(fā)現(xiàn)一個(gè)小伙伴定義的出參是個(gè)枚舉值,但是接口文檔沒(méi)有給出對(duì)應(yīng)具體的枚舉值。其實(shí),如何寫好接口文檔,真的很重要。今天田螺哥,給你帶來(lái)接口設(shè)計(jì)文檔的12個(gè)注意點(diǎn)~

了解一下Golang中的unsafe包 了解一下Golang中的unsafe包 Apr 02, 2023 am 08:30 AM

在一些底層的庫(kù)中, 經(jīng)常會(huì)看到使用 unsafe 包的地方。本篇文章就來(lái)帶大家了解一下Golang中的unsafe包,介紹一下unsafe 包的作用和Pointer的使用方式,希望對(duì)大家有所幫助!

深入聊聊Node中的File模塊 深入聊聊Node中的File模塊 Apr 24, 2023 pm 05:49 PM

文件模塊是對(duì)底層文件操作的封裝,例如文件讀寫/打開關(guān)閉/刪除添加等等 文件模塊最大的特點(diǎn)就是所有的方法都提供的**同步**和**異步**兩個(gè)版本,具有 sync 后綴的方法都是同步方法,沒(méi)有的都是異

深入了解Node中的Buffer 深入了解Node中的Buffer Apr 25, 2023 pm 07:49 PM

最開始的時(shí)候 JS 只在瀏覽器端運(yùn)行,對(duì)于 Unicode 編碼的字符串容易處理,但是對(duì)于二進(jìn)制和非 Unicode 編碼的字符串處理困難。并且二進(jìn)制是計(jì)算機(jī)最底層的數(shù)據(jù)格式,視頻/音頻/程序/網(wǎng)絡(luò)包

See all articles