V8引擎的內(nèi)存管理

在本章中,我們將介紹用于ECMAScript和WebAssembly的V8引擎的內(nèi)存管理穷躁,這些引擎用于NodeJS忍抽、Deno&Electron等運(yùn)行時(shí),以及Chrome插掂、Chromium灰瞻、Brave、Opera和Microsoft Edge等web瀏覽器辅甥。由于JavaScript是一種解釋性語(yǔ)言酝润,它需要一個(gè)引擎來解釋和執(zhí)行代碼。V8引擎解釋JavaScript并將其編譯為機(jī)器代碼璃弄。V8是用C++編寫的要销,可以嵌入任何C++應(yīng)用程序中。

首先夏块,我們來看看V8引擎的內(nèi)存結(jié)構(gòu)疏咐。由于JavaScript是單線程語(yǔ)言,所以V8為每一個(gè)JavaScript上下文使用一個(gè)進(jìn)程脐供。如果你使用service worker浑塞,V8會(huì)為每個(gè)service worker開啟一個(gè)新的進(jìn)程。在V8進(jìn)程中政己,一個(gè)正在運(yùn)行的程序總是由一些分配的內(nèi)存來表示酌壕,這稱為常駐集Resident Set)⌒桑可以進(jìn)一步劃分以下不同的部分:

這和我們?cè)谏弦黄恼轮刑岬降腏VM有些相似卵牍。我們來看一看每一個(gè)部分都是做什么的:

堆內(nèi)存(Heap memory)

這是V8存儲(chǔ)對(duì)象和動(dòng)態(tài)數(shù)據(jù)的地方。這是內(nèi)存中區(qū)域中最大的塊沦泌,也是垃圾回收(GC)發(fā)生的地方辽慕。整個(gè)堆內(nèi)存不是垃圾回收的,只有新舊空間(New space赦肃、Old space)是垃圾回收管理的溅蛉。堆內(nèi)存可以進(jìn)一步劃分為以下幾部分:

1. 新空間(New space)

新空間(或者說叫:新生代)公浪,是存儲(chǔ)新對(duì)象的地方,并且大部分對(duì)象的聲明周期都很短船侧。這個(gè)空間很小欠气,有兩個(gè)半空間,類似于JVM中的S0镜撩,S1预柒。這片空間是由**Scavenger(Minor GC)**來管理的,稍后會(huì)介紹袁梗。新生代空間的大小可以由--min_semi_space_size(初始值) 和 --max_semi_space_size(最大值)兩個(gè)V8標(biāo)志來控制宜鸯。

2. 老空間(Old space)

老空間(或者說叫:老生代),存儲(chǔ)的是在新生代空間中經(jīng)過了兩次Minor GC后存活下來的數(shù)據(jù)遮怜。這片空間是由**Major GC(Mark-Sweep & Mark-Compact)”**管理的淋袖,稍后會(huì)介紹。老生代空間的大小可以--initial_old_space_size(初始值)and --max_old_space_size(最大值) 兩個(gè)V8標(biāo)志來控制锯梁。這片空間被分成了兩個(gè)部分:

????老指針空間(Old pointer space):包含了存活下來的包含指向其他對(duì)象指針的對(duì)象即碗。

????老數(shù)據(jù)空間(Old data space):包含了盡保存數(shù)據(jù)的對(duì)象(沒有指向其他對(duì)象的指針)。字符串陌凳,已裝箱的數(shù)字剥懒,未裝箱的雙精度數(shù)組,在新生代空間經(jīng)過兩輪Minor GC后存活下來的合敦,會(huì)被移到老數(shù)據(jù)空間初橘。

3. 大對(duì)象空間(Large object space)

這是大于其他空間大小限制的對(duì)象存儲(chǔ)的地方。每個(gè)對(duì)象都有自己的內(nèi)存區(qū)域充岛。大對(duì)象是不會(huì)被垃圾回收的保檐。

4. 代碼空間(Code-space)

這就是即時(shí)(JIT)編譯器存儲(chǔ)編譯代碼塊的地方。這是唯一有可執(zhí)行內(nèi)存的空間(盡管代碼可能被分配在“大對(duì)象空間”中裸准,它們也是可執(zhí)行的)。

5. 單元空間赔硫、屬性單元空間炒俱、映射空間(Cell space, property cell space, and map space)

這些空間分別包含Cell,PropertyCell 和 Map. 這些空間中的每一個(gè)都包含相同大小的對(duì)象爪膊,并且對(duì)它們指向的對(duì)象類型有一些限制权悟,這簡(jiǎn)化了收集。

每個(gè)空間都由一組頁(yè)組成推盛。頁(yè)是使用mmap從操作系統(tǒng)分配的連續(xù)內(nèi)存塊峦阁。每頁(yè)大小為1MB,但大對(duì)象空間較大耘成。

棧(Stack)

這是棧內(nèi)存區(qū)域榔昔,每個(gè)V8進(jìn)程有一個(gè)棧驹闰。這里存儲(chǔ)靜態(tài)數(shù)據(jù),包括方法/函數(shù)框架撒会、原語(yǔ)值和指向?qū)ο蟮闹羔樴诶省?nèi)存限制可以使用–stack_size V8標(biāo)志設(shè)置。

V8的內(nèi)存使用(棧 VS 堆)

既然我們已經(jīng)清楚了內(nèi)存是如何組織的诵肛,讓我們看看在執(zhí)行程序時(shí)如何使用其中最重要的部分屹培。

讓我們使用下面的JavaScript程序,代碼沒有針對(duì)正確性進(jìn)行優(yōu)化怔檩,因此忽略了不必要的中間變量等問題褪秀,重點(diǎn)是可視化棧和堆內(nèi)存的使用情況。

classEmployee?{

????constructor(name,?salary,?sales)?{

????????this.name?=?name;

????????this.salary?=?salary;

????????this.sales?=?sales;

????}

}

constBONUS_PERCENTAGE?=?10;

functiongetBonusPercentage(salary)?{

????constpercentage?=?(salary?*?BONUS_PERCENTAGE)?/?100;

????returnpercentage;

}

functionfindEmployeeBonus(salary,?noOfSales)?{

????constbonusPercentage?=?getBonusPercentage(salary);

????constbonus?=?bonusPercentage?*?noOfSales;

????returnbonus;

}

letjohn?=?newEmployee("John",?5000,?5);

john.bonus?=?findEmployeeBonus(john.salary,?john.sales);

console.log(john.bonus);

可以通過下面的ppt看一下在上面的代碼執(zhí)行的過程中薛训,棧內(nèi)存和堆內(nèi)存是如何使用的媒吗。

如你所見:

1.全局作用域保存在棧上的全局框架(Global frame)中。

2.?每個(gè)函數(shù)調(diào)用都作為幀塊添加到堆棧內(nèi)存中许蓖。

3.?所有局部變量(包括參數(shù)和返回值)都保存在棧的函數(shù)框塊中蝴猪。

4.?像int&string這樣的所有基元類型都直接存儲(chǔ)在棧上。這同樣適用于全局作用域膊爪。

5.?當(dāng)前函數(shù)調(diào)用的函數(shù)將被推到棧的頂部自阱。

6.?當(dāng)函數(shù)返回時(shí),它的框架幀塊將被移除米酬。

7.?一旦主進(jìn)程完成沛豌,堆上的對(duì)象就不再有來自棧的指針,成為孤立的對(duì)象赃额。

8.?除非顯式復(fù)制加派,否則其他對(duì)象中的所有對(duì)象引用都是使用引用指針完成的。

如你所見跳芳,棧是由操作系統(tǒng)自動(dòng)管理的芍锦,而不是V8。因此飞盆,我們不必太擔(dān)心棧娄琉。另一方面,堆并不是由操作系統(tǒng)自動(dòng)管理的吓歇,因?yàn)槎咽亲畲蟮膬?nèi)存空間孽水,并保存動(dòng)態(tài)數(shù)據(jù),它可能會(huì)隨著時(shí)間的推移呈指數(shù)增長(zhǎng)城看,導(dǎo)致我們的程序內(nèi)存耗盡女气。隨著時(shí)間的推移,它也變得支離破碎测柠,減慢了應(yīng)用程序的速度炼鞠。這就是為什么需要垃圾回收缘滥。

區(qū)分堆上的指針和數(shù)據(jù)對(duì)于垃圾收集很重要,V8使用“標(biāo)記指針”方法來實(shí)現(xiàn)這一點(diǎn)簇搅。在這種方法中完域,它在每個(gè)單詞的末尾保留一個(gè)位,以指示它是指針還是數(shù)據(jù)瘩将。這種方法需要有限的編譯器支持吟税,但實(shí)現(xiàn)起來很簡(jiǎn)單,同時(shí)效率也相當(dāng)高姿现。

V8內(nèi)存管理 - 垃圾回收(GC)

現(xiàn)在我們知道了V8如何分配內(nèi)存肠仪,讓我們看看它如何自動(dòng)管理堆內(nèi)存,這對(duì)應(yīng)用程序的性能非常重要备典。當(dāng)一個(gè)程序試圖在堆上分配比自由可用的更多的內(nèi)存(取決于V8標(biāo)志集)時(shí)异旧,我們會(huì)遇到內(nèi)存不足的錯(cuò)誤。錯(cuò)誤管理的堆也可能導(dǎo)致內(nèi)存泄漏提佣。

V8通過垃圾收集來管理堆內(nèi)存吮蛹。簡(jiǎn)單地說,它釋放孤立對(duì)象(即不再直接或間接從堆棧中引用的對(duì)象(通過另一個(gè)對(duì)象中的引用)使用的內(nèi)存拌屏,以便為創(chuàng)建新對(duì)象騰出空間潮针。

Orinoco是V8 GC項(xiàng)目的代碼名,用于使用并行倚喂、增量和并發(fā)的垃圾回收技術(shù)來釋放主線程每篷。

V8中的垃圾回收器負(fù)責(zé)回收未使用的內(nèi)存,供V8進(jìn)程重用端圈。

V8垃圾回收器是分代的(堆中的對(duì)象按其年齡分組并在不同階段清除)焦读。V8有兩個(gè)階段和三種不同的垃圾收集算法:

Minor GC (Scavenger)

這種類型的GC保持新生代空間的緊湊和清潔。對(duì)象被分配到相當(dāng)小的空間(1到8MB之間舱权,取決于行為啟發(fā))矗晃。新生代空間的分配成本很低:有一個(gè)分配指針,每當(dāng)我們想為新對(duì)象保留空間時(shí)宴倍,它都會(huì)遞增张症。當(dāng)分配指針到達(dá)新生代空間的末尾時(shí),將觸發(fā)次Minor GC啊楚。這個(gè)過程被稱為Scavenger吠冤,實(shí)現(xiàn)了“切尼算法”浑彰。Minor GC經(jīng)常出現(xiàn)并使用并行的輔助線程恭理,而且速度非常快郭变。

讓我們來看一看Minor GC的過程:

新生代空間被分成兩個(gè)大小相等的半空間:from-space和to-space颜价。大多數(shù)分配都是在to-space中進(jìn)行的(除了某些類型的對(duì)象涯保,例如總是在老生代空間中分配的可執(zhí)行代碼)。當(dāng)to-space填滿時(shí)周伦,將觸發(fā)Minor GC夕春。完成過程如下:

1. 當(dāng)我們開始時(shí),假設(shè)to-space里已經(jīng)有對(duì)象了专挪。

2. 進(jìn)程創(chuàng)建了一個(gè)新的對(duì)象及志。

3. V8試圖從to-space獲取所需的內(nèi)存,但其中沒有可用空間來容納我們的對(duì)象寨腔,因此V8觸發(fā)了Minor GC速侈。

4. Minor GC交換to-space和from-space,所有對(duì)象現(xiàn)在都在from-space中迫卢,to space為空倚搬。

5. Minor GC遞歸地從堆棧指針(GC根)開始遍歷from-space中的對(duì)象圖,以查找已使用或活動(dòng)的對(duì)象(已用內(nèi)存)乾蛤。這些對(duì)象將移動(dòng)到to-space的頁(yè)中每界。由這些對(duì)象引用的任何對(duì)象也會(huì)在to-space中移動(dòng)到此頁(yè),并且它們的指針會(huì)更新家卖。重復(fù)此操作眨层,直到from-space中的對(duì)象都被掃描一次。最終篡九,to-space被自動(dòng)壓縮以減少碎片谐岁。

6. Minor GC現(xiàn)在清空from-space,因?yàn)檫@里的任何剩余對(duì)象都是垃圾榛臼。

7. 新對(duì)象被分配到to-space的內(nèi)存空間中伊佃。

8. 讓我們假設(shè)過了一段時(shí)間,to-space中的對(duì)象更多了沛善。

9. 應(yīng)用又新建了一個(gè)對(duì)象航揉。

10. V8試圖從to-space獲取所需的內(nèi)存,但其中沒有可用空間來容納我們的對(duì)象金刁,因此V8觸發(fā)了第二次Minor GC帅涂。

11. 重復(fù)上述過程,并將第二個(gè)Minor GC中幸存的任何活動(dòng)對(duì)象移動(dòng)到老生代空間尤蛮。第一次Minor GC的幸存者被轉(zhuǎn)移到to-space媳友,剩余的垃圾從from-space中被清除。

12. 新對(duì)象被分配到to-space的內(nèi)存空間中产捞。

我們看到了Minor GC如何從新生代內(nèi)存空間那里回收空間并使其保持緊湊的醇锚。這個(gè)過程雖然會(huì)停止其他操作,但是這個(gè)過程是十分迅速而有效的,大部分時(shí)候都微不足道焊唬。由于此進(jìn)程不掃描老生代空間中的對(duì)象以獲取新生代空間中的任何引用恋昼,因此它使用從老生代空間到新生代空間的所有指針的寄存器。這將由一個(gè)名為write barriers的進(jìn)程記錄到存儲(chǔ)緩沖區(qū)赶促。

Major GC

這種類型的GC保持了老生代空間的緊湊和干凈液肌。當(dāng)V8根據(jù)動(dòng)態(tài)計(jì)算的限制確定沒有足夠的老生代空間時(shí),就會(huì)觸發(fā)此操作鸥滨,因?yàn)樗菑腗inor GC周期中填充的嗦哆。

Scavenger算法非常適合于較小的數(shù)據(jù)量,但對(duì)于較大的老生代空間來說是不實(shí)際的婿滓,因?yàn)樗袃?nèi)存開銷吝秕,因此主要的GC是使用Mark-Sweep-Compact算法完成的。它使用三色(白灰黑)標(biāo)記系統(tǒng)空幻。因此烁峭,Major GC是一個(gè)三步過程,第三步是根據(jù)分段啟發(fā)執(zhí)行的秕铛。

1. 標(biāo)記:第一步约郁,兩種算法都通用,其中垃圾回收器標(biāo)識(shí)哪些對(duì)象正在使用但两,哪些對(duì)象未在使用鬓梅。遞歸地從GC根(棧指針)中使用中或可訪問的對(duì)象被標(biāo)記為活動(dòng)的。從技術(shù)上講谨湘,這是對(duì)堆的深度優(yōu)先搜索绽快,可以看作是有向圖。

2. 清理:垃圾回收器遍歷堆并記錄任何未標(biāo)記為活動(dòng)的對(duì)象的內(nèi)存地址紧阔。這些空間現(xiàn)在在空閑列表中被標(biāo)記為空閑坊罢,可用于存儲(chǔ)其他對(duì)象。

3. 壓縮:清理后擅耽,如果需要活孩,將所有剩下的對(duì)象移動(dòng)到一起。這將減少碎片并提高向較新對(duì)象分配內(nèi)存的性能乖仇。

這種類型的GC也稱為stop-the-world GC憾儒,因?yàn)樗鼈冊(cè)趫?zhí)行GC的過程中引入了暫停時(shí)間。為了避免這個(gè)V8使用了如下技術(shù):

1. 增量GC:GC是以多個(gè)增量步驟而不是一個(gè)增量步驟完成的乃沙。

2. 并發(fā)標(biāo)記:標(biāo)記是在不影響主JavaScript線程的情況下使用多個(gè)輔助線程并發(fā)完成的起趾。Write barriers用于跟蹤JavaScript在幫助程序并發(fā)標(biāo)記時(shí)創(chuàng)建的對(duì)象之間的新引用。

3. 并發(fā)掃描/壓縮:掃描和壓縮在助手線程中同時(shí)完成警儒,而不影響主JavaScript線程训裆。

4. 延遲清理:延遲清理,包括延遲刪除頁(yè)中的垃圾,直到需要內(nèi)存為止缭保。

讓我們來看一下 major GC的過程:

1. 讓我們假設(shè)許多Minor GC周期已經(jīng)過去,舊空間幾乎滿了蝙茶,V8決定觸發(fā)一個(gè)Major GC

2. Major GC從棧指針開始遞歸地遍歷對(duì)象圖艺骂,以標(biāo)記在老生代空間中用作活動(dòng)(已用內(nèi)存)和剩余對(duì)象作為垃圾(孤立)的對(duì)象。這是使用多個(gè)并發(fā)助手線程完成的隆夯,每個(gè)助手都跟隨一個(gè)指針钳恕。這不會(huì)影響主JS線程。

3. 當(dāng)并發(fā)標(biāo)記完成或達(dá)到內(nèi)存限制時(shí)蹄衷,GC使用主線程執(zhí)行標(biāo)記終結(jié)步驟忧额。這將引入一個(gè)小的暫停時(shí)間。

4. Major GC現(xiàn)在使用并發(fā)掃描線程將所有孤立對(duì)象的內(nèi)存標(biāo)記為空閑愧口。并行壓縮任務(wù)也會(huì)被觸發(fā)睦番,以將相關(guān)內(nèi)存塊移動(dòng)到同一頁(yè)以避免碎片化。在這些步驟中會(huì)更新指針耍属。

結(jié)論

本文將為您提供V8內(nèi)存結(jié)構(gòu)和內(nèi)存管理的概述托嚣。這里沒有做到面面俱到的,還有很多更高級(jí)的概念厚骗,您可以從v8.dev中了解它們示启。但是對(duì)于大多數(shù)JS/WebAssembly開發(fā)人員來說,這一級(jí)別的信息就足夠了领舰,我希望它能幫助您編寫更好的代碼夫嗓,考慮到這些因素,對(duì)于更高性能的應(yīng)用程序冲秽,記住這些可以幫助您避免下一個(gè)可能遇到的內(nèi)存泄漏問題舍咖。

原文地址:https://deepu.tech/memory-management-in-v8/

原文標(biāo)題:Visualizing memory management in V8 Engine (JavaScript, NodeJS, Deno, WebAssembly)

譯者:Coy_Pan

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市锉桑,隨后出現(xiàn)的幾起案子谎仲,更是在濱河造成了極大的恐慌,老刑警劉巖刨仑,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郑诺,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡杉武,警方通過查閱死者的電腦和手機(jī)辙诞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轻抱,“玉大人飞涂,你說我怎么就攤上這事。” “怎么了较店?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵士八,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我梁呈,道長(zhǎng)婚度,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任官卡,我火速辦了婚禮蝗茁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寻咒。我一直安慰自己哮翘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布毛秘。 她就那樣靜靜地躺著饭寺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叫挟。 梳的紋絲不亂的頭發(fā)上佩研,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音霞揉,去河邊找鬼旬薯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛适秩,可吹牛的內(nèi)容都是我干的绊序。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼秽荞,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼骤公!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起扬跋,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤阶捆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后钦听,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體洒试,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年朴上,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了垒棋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡痪宰,死狀恐怖叼架,靈堂內(nèi)的尸體忽然破棺而出畔裕,到底是詐尸還是另有隱情,我是刑警寧澤乖订,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布扮饶,位于F島的核電站,受9級(jí)特大地震影響乍构,放射性物質(zhì)發(fā)生泄漏甜无。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一蜡吧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧占键,春花似錦昔善、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至牲距,卻和暖如春返咱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背牍鞠。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工咖摹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人难述。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓萤晴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親胁后。 傳聞我的和親對(duì)象是個(gè)殘疾皇子店读,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348