摘要:新生代的對(duì)象為存活時(shí)間較短的對(duì)象,老生代中的對(duì)象為存活時(shí)間較長(zhǎng)或常駐內(nèi)存的對(duì)象。分別對(duì)新生代和老生代使用不同的垃圾回收算法來(lái)提升垃圾回收的效率。如果指向老生代我們就不必考慮它了。
這篇文章的所有內(nèi)容均來(lái)自 樸靈的《深入淺出Node.js》及A tour of V8:Garbage Collection,后者還有中文翻譯版V8 之旅: 垃圾回收器,我在這里只是做了個(gè)記錄和結(jié)合
垃圾回收器 JavaScript的垃圾回收器JavaScript使用垃圾回收機(jī)制來(lái)自動(dòng)管理內(nèi)存。垃圾回收是一把雙刃劍,其好處是可以大幅簡(jiǎn)化程序的內(nèi)存管理代碼,降低程序員的負(fù)擔(dān),減少因長(zhǎng)時(shí)間運(yùn)轉(zhuǎn)而帶來(lái)的內(nèi)存泄露問(wèn)題。但使用了垃圾回收即意味著程序員將無(wú)法掌控內(nèi)存。ECMAScript沒(méi)有暴露任何垃圾回收器的接口。我們無(wú)法強(qiáng)迫其進(jìn)行垃圾回收,更無(wú)法干預(yù)內(nèi)存管理
Node的內(nèi)存管理問(wèn)題在瀏覽器中,V8引擎實(shí)例的生命周期不會(huì)很長(zhǎng)(誰(shuí)沒(méi)事一個(gè)頁(yè)面開(kāi)著幾天幾個(gè)月不關(guān)),而且運(yùn)行在用戶的機(jī)器上。如果不幸發(fā)生內(nèi)存泄露等問(wèn)題,僅僅會(huì)影響到一個(gè)終端用戶。且無(wú)論這個(gè)V8實(shí)例占用了多少內(nèi)存,最終在關(guān)閉頁(yè)面時(shí)內(nèi)存都會(huì)被釋放,幾乎沒(méi)有太多管理的必要(當(dāng)然并不代表一些大型Web應(yīng)用不需要管理內(nèi)存)。但如果使用Node作為服務(wù)器,就需要關(guān)注內(nèi)存問(wèn)題了,一旦內(nèi)存發(fā)生泄漏,久而久之整個(gè)服務(wù)將會(huì)癱瘓(服務(wù)器不會(huì)頻繁的重啟)
V8的內(nèi)存限制 存在限制Node與其他語(yǔ)言不同的一個(gè)地方,就是其限制了JavaScript所能使用的內(nèi)存(64位為1.4GB,32位為0.7GB),這也就意味著將無(wú)法直接操作一些大內(nèi)存對(duì)象。這很令人匪夷所思,因?yàn)楹苌儆衅渌Z(yǔ)言會(huì)限制內(nèi)存的使用
為何限制V8之所以限制了內(nèi)存的大小,表面上的原因是V8最初是作為瀏覽器的JavaScript引擎而設(shè)計(jì),不太可能遇到大量?jī)?nèi)存的場(chǎng)景,而深層次的原因則是由于V8的垃圾回收機(jī)制的限制。由于V8需要保證JavaScript應(yīng)用邏輯與垃圾回收器所看到的不一樣,V8在執(zhí)行垃圾回收時(shí)會(huì)阻塞JavaScript應(yīng)用邏輯,直到垃圾回收結(jié)束再重新執(zhí)行JavaScript應(yīng)用邏輯,這種行為被稱為“全停頓”(stop-the-world)。若V8的堆內(nèi)存為1.5GB,V8做一次小的垃圾回收需要50ms以上,做一次非增量式的垃圾回收甚至要1秒以上。這樣瀏覽器將在1s內(nèi)失去對(duì)用戶的響應(yīng),造成假死現(xiàn)象。如果有動(dòng)畫(huà)效果的話,動(dòng)畫(huà)的展現(xiàn)也將顯著受到影響
突破限制當(dāng)然這個(gè)限制是可以打開(kāi)的,類似于JVM,我們通過(guò)在啟動(dòng)node時(shí)可以傳遞--max-old-space-size或--max-new-space-size來(lái)調(diào)整內(nèi)存限制的大小,前者確定老生代的大小,單位為MB,后者確定新生代的大小,單位為KB。這些配置只在V8初始化時(shí)生效,一旦生效不能再改變
V8的堆構(gòu)成V8的堆其實(shí)并不只是由老生代和新生代兩部分構(gòu)成,可以將堆分為幾個(gè)不同的區(qū)域:
* 新生代內(nèi)存區(qū):大多數(shù)的對(duì)象被分配在這里,這個(gè)區(qū)域很小但是垃圾回特別頻繁
* 老生代指針區(qū):屬于老生代,這里包含了大多數(shù)可能存在指向其他對(duì)象的指針的對(duì)象,大多數(shù)從新生代晉升的對(duì)象會(huì)被移動(dòng)到這里
* 老生代數(shù)據(jù)區(qū):屬于老生代,這里只保存原始數(shù)據(jù)對(duì)象,這些對(duì)象沒(méi)有指向其他對(duì)象的指針
* 大對(duì)象區(qū):這里存放體積超越其他區(qū)大小的對(duì)象,每個(gè)對(duì)象有自己的內(nèi)存,垃圾回收其不會(huì)移動(dòng)大對(duì)象
* 代碼區(qū):代碼對(duì)象,也就是包含JIT之后指令的對(duì)象,會(huì)被分配在這里。唯一擁有執(zhí)行權(quán)限的內(nèi)存區(qū)
* Cell區(qū)、屬性Cell區(qū)、Map區(qū):存放Cell、屬性Cell和Map,每個(gè)區(qū)域都是存放相同大小的元素,結(jié)構(gòu)簡(jiǎn)單
每個(gè)區(qū)域都是由一組內(nèi)存頁(yè)構(gòu)成,內(nèi)存頁(yè)是V8申請(qǐng)內(nèi)存的最小單位,除了大對(duì)象區(qū)的內(nèi)存頁(yè)較大以外,其他區(qū)的內(nèi)存頁(yè)都是1MB大小,而且按照1MB對(duì)齊。內(nèi)存頁(yè)除了存儲(chǔ)的對(duì)象,還有一個(gè)包含元數(shù)據(jù)和標(biāo)識(shí)信息的頁(yè)頭,以及一個(gè)用于標(biāo)記哪些對(duì)象是活躍對(duì)象的位圖區(qū)。另外每個(gè)內(nèi)存頁(yè)還有一個(gè)多帶帶分配在另外內(nèi)存區(qū)的槽緩沖區(qū),里面放著一組對(duì)象,這些對(duì)象可能指向其他存儲(chǔ)在該頁(yè)的對(duì)象。垃圾回收器只會(huì)針對(duì)新生代內(nèi)存區(qū)、老生代指針區(qū)以及老生代數(shù)據(jù)區(qū)進(jìn)行垃圾回收
V8的垃圾回收機(jī)制 如何判斷回收內(nèi)容如何確定哪些內(nèi)存需要回收,哪些內(nèi)存不需要回收,這是垃圾回收期需要解決的最基本問(wèn)題。我們可以這樣假定,一個(gè)對(duì)象為活對(duì)象當(dāng)且僅當(dāng)它被一個(gè)根對(duì)象或另一個(gè)活對(duì)象指向。根對(duì)象永遠(yuǎn)是活對(duì)象,它是被瀏覽器或V8所引用的對(duì)象。被局部變量所指向的對(duì)象也屬于根對(duì)象,因?yàn)樗鼈兯诘淖饔糜驅(qū)ο蟊灰暈楦鶎?duì)象。全局對(duì)象(Node中為global,瀏覽器中為window)自然是根對(duì)象。瀏覽器中的DOM元素也屬于根對(duì)象
如何識(shí)別指針和數(shù)據(jù)垃圾回收器需要面臨一個(gè)問(wèn)題,它需要判斷哪些是數(shù)據(jù),哪些是指針。由于很多垃圾回收算法會(huì)將對(duì)象在內(nèi)存中移動(dòng)(緊湊,減少內(nèi)存碎片),所以經(jīng)常需要進(jìn)行指針的改寫(xiě)
目前主要有三種方法來(lái)識(shí)別指針:
1. 保守法:將所有堆上對(duì)齊的字都認(rèn)為是指針,那么有些數(shù)據(jù)就會(huì)被誤認(rèn)為是指針。于是某些實(shí)際是數(shù)字的假指針,會(huì)背誤認(rèn)為指向活躍對(duì)象,導(dǎo)致內(nèi)存泄露(假指針指向的對(duì)象可能是死對(duì)象,但依舊有指針指向——這個(gè)假指針指向它)同時(shí)我們不能移動(dòng)任何內(nèi)存區(qū)域。
2. 編譯器提示法:如果是靜態(tài)語(yǔ)言,編譯器能夠告訴我們每個(gè)類當(dāng)中指針的具體位置,而一旦我們知道對(duì)象時(shí)哪個(gè)類實(shí)例化得到的,就能知道對(duì)象中所有指針。這是JVM實(shí)現(xiàn)垃圾回收的方式,但這種方式并不適合JS這樣的動(dòng)態(tài)語(yǔ)言
3. 標(biāo)記指針?lè)ǎ哼@種方法需要在每個(gè)字末位預(yù)留一位來(lái)標(biāo)記這個(gè)字段是指針還是數(shù)據(jù)。這種方法需要編譯器支持,但實(shí)現(xiàn)簡(jiǎn)單,而且性能不錯(cuò)。V8采用的是這種方式。V8將所有數(shù)據(jù)以32bit字寬來(lái)存儲(chǔ),其中最低一位保持為0,而指針的最低兩位為01
自動(dòng)垃圾回收算法的演變過(guò)程中出現(xiàn)了很多算法,但是由于不同對(duì)象的生存周期不同,沒(méi)有一種算法適用于所有的情況。所以V8采用了一種分代回收的策略,將內(nèi)存分為兩個(gè)生代:新生代和老生代。新生代的對(duì)象為存活時(shí)間較短的對(duì)象,老生代中的對(duì)象為存活時(shí)間較長(zhǎng)或常駐內(nèi)存的對(duì)象。分別對(duì)新生代和老生代使用不同的垃圾回收算法來(lái)提升垃圾回收的效率。對(duì)象起初都會(huì)被分配到新生代,當(dāng)新生代中的對(duì)象滿足某些條件(后面會(huì)有介紹)時(shí),會(huì)被移動(dòng)到老生代(晉升)
V8的分代內(nèi)存默認(rèn)情況下,64位環(huán)境下的V8引擎的新生代內(nèi)存大小32MB、老生代內(nèi)存大小為1400MB,而32位則減半,分別為16MB和700MB。V8內(nèi)存的最大保留空間分別為1464MB(64位)和732MB(32位)。具體的計(jì)算公式是4*reserved_semispace_space_ + max_old_generation_size_,新生代由兩塊reserved_semispace_space_組成,每塊16MB(64位)或8MB(32位)
新生代 新生代的特點(diǎn)大多數(shù)的對(duì)象被分配在這里,這個(gè)區(qū)域很小但是垃圾回特別頻繁。在新生代分配內(nèi)存非常容易,我們只需要保存一個(gè)指向內(nèi)存區(qū)的指針,不斷根據(jù)新對(duì)象的大小進(jìn)行遞增即可。當(dāng)該指針到達(dá)了新生代內(nèi)存區(qū)的末尾,就會(huì)有一次清理(僅僅是清理新生代)
新生代的垃圾回收算法新生代使用Scavenge算法進(jìn)行回收。在Scavenge算法的實(shí)現(xiàn)中,主要采用了Cheney算法。
Cheney算法算法是一種采用復(fù)制的方式實(shí)現(xiàn)的垃圾回收算法。它將內(nèi)存一分為二,每一部分空間稱為semispace。在這兩個(gè)semispace中,一個(gè)處于使用狀態(tài),另一個(gè)處于閑置狀態(tài)。處于使用狀態(tài)的semispace空間稱為From空間,處于閑置狀態(tài)的空間稱為To空間,當(dāng)我們分配對(duì)象時(shí),先是在From空間中進(jìn)行分配。當(dāng)開(kāi)始進(jìn)行垃圾回收算法時(shí),會(huì)檢查From空間中的存活對(duì)象,這些存活對(duì)象將會(huì)被復(fù)制到To空間中(復(fù)制完成后會(huì)進(jìn)行緊縮),而非活躍對(duì)象占用的空間將會(huì)被釋放。完成復(fù)制后,F(xiàn)rom空間和To空間的角色發(fā)生對(duì)換。也就是說(shuō),在垃圾回收的過(guò)程中,就是通過(guò)將存活對(duì)象在兩個(gè)semispace之間進(jìn)行復(fù)制??梢院苋菀卓闯鰜?lái),使用Cheney算法時(shí),總有一半的內(nèi)存是空的。但是由于新生代很小,所以浪費(fèi)的內(nèi)存空間并不大。而且由于新生代中的對(duì)象絕大部分都是非活躍對(duì)象,需要復(fù)制的活躍對(duì)象比例很小,所以其時(shí)間效率十分理想。復(fù)制的過(guò)程采用的是BFS(廣度優(yōu)先遍歷)的思想,從根對(duì)象出發(fā),廣度優(yōu)先遍歷所有能到達(dá)的對(duì)象
具體的執(zhí)行過(guò)程大致是這樣:
首先將From空間中所有能從根對(duì)象到達(dá)的對(duì)象復(fù)制到To區(qū),然后維護(hù)兩個(gè)To區(qū)的指針scanPtr和allocationPtr,分別指向即將掃描的活躍對(duì)象和即將為新對(duì)象分配內(nèi)存的地方,開(kāi)始循環(huán)。循環(huán)的每一輪會(huì)查找當(dāng)前scanPtr所指向的對(duì)象,確定對(duì)象內(nèi)部的每個(gè)指針指向哪里。如果指向老生代我們就不必考慮它了。如果指向From區(qū),我們就需要把這個(gè)所指向的對(duì)象從From區(qū)復(fù)制到To區(qū),具體復(fù)制的位置就是allocationPtr所指向的位置。復(fù)制完成后將scanPtr所指對(duì)象內(nèi)的指針修改為新復(fù)制對(duì)象存放的地址,并移動(dòng)allocationPtr。如果一個(gè)對(duì)象內(nèi)部的所有指針都被處理完,scanPtr就會(huì)向前移動(dòng),進(jìn)入下一個(gè)循環(huán)。若scanPtr和allocationPtr相遇,則說(shuō)明所有的對(duì)象都已被復(fù)制完,F(xiàn)rom區(qū)剩下的都可以被視為垃圾,可以進(jìn)行清理了
舉個(gè)栗子(以及湊篇幅),如果有類似如下的引用情況:
+----- A對(duì)象 | 根對(duì)象----+----- B對(duì)象 ------ E對(duì)象 | +----- C對(duì)象 ----+---- F對(duì)象 | +---- G對(duì)象 ----- H對(duì)象 D對(duì)象
在執(zhí)行Scavenge之前,F(xiàn)rom區(qū)長(zhǎng)這幅模樣
+---+---+---+---+---+---+---+---+--------+ | A | B | C | D | E | F | G | H | | +---+---+---+---+---+---+---+---+--------+
那么首先將根對(duì)象能到達(dá)的ABC對(duì)象復(fù)制到To區(qū),于是乎To區(qū)就變成了這個(gè)樣子:
allocationPtr ↓ +---+---+---+----------------------------+ | A | B | C | | +---+---+---+----------------------------+ ↑ scanPtr
接下來(lái)進(jìn)入循環(huán),掃描scanPtr所指的A對(duì)象,發(fā)現(xiàn)其沒(méi)有指針,于是乎scanPtr移動(dòng),變成如下這樣
allocationPtr ↓ +---+---+---+----------------------------+ | A | B | C | | +---+---+---+----------------------------+ ↑ scanPtr
接下來(lái)掃描B對(duì)象,發(fā)現(xiàn)其有指向E對(duì)象的指針,且E對(duì)象在From區(qū),那么我們需要將E對(duì)象復(fù)制到allocationPtr所指的地方并移動(dòng)allocationPtr指針:
allocationPtr ↓ +---+---+---+---+------------------------+ | A | B | C | E | | +---+---+---+---+------------------------+ ↑ scanPtr
B對(duì)象里所有指針都已被復(fù)制完,所以移動(dòng)scanPtr:
allocationPtr ↓ +---+---+---+---+------------------------+ | A | B | C | E | | +---+---+---+---+------------------------+ ↑ scanPtr
接下來(lái)掃描C對(duì)象,C對(duì)象中有兩個(gè)指針,分別指向F對(duì)象和G對(duì)象,且都在From區(qū),先復(fù)制F對(duì)象到To區(qū):
allocationPtr ↓ +---+---+---+---+---+--------------------+ | A | B | C | E | F | | +---+---+---+---+---+--------------------+ ↑ scanPtr
然后復(fù)制G對(duì)象到To區(qū)
allocationPtr ↓ +---+---+---+---+---+---+----------------+ | A | B | C | E | F | G | | +---+---+---+---+---+---+----------------+ ↑ scanPtr
這樣C對(duì)象內(nèi)部的指針已經(jīng)復(fù)制完成了,移動(dòng)scanPtr:
allocationPtr ↓ +---+---+---+---+---+---+----------------+ | A | B | C | E | F | G | | +---+---+---+---+---+---+----------------+ ↑ scanPtr
逐個(gè)掃描E,F(xiàn)對(duì)象,發(fā)現(xiàn)其中都沒(méi)有指針,移動(dòng)scanPtr:
allocationPtr ↓ +---+---+---+---+---+---+----------------+ | A | B | C | E | F | G | | +---+---+---+---+---+---+----------------+ ↑ scanPtr
掃描G對(duì)象,發(fā)現(xiàn)其中有一個(gè)指向H對(duì)象的指針,且H對(duì)象在From區(qū),復(fù)制H對(duì)象到To區(qū),并移動(dòng)allocationPtr:
allocationPtr ↓ +---+---+---+---+---+---+---+------------+ | A | B | C | E | F | G | H | | +---+---+---+---+---+---+---+------------+ ↑ scanPtr
完成后由于G對(duì)象沒(méi)有其他指針,且H對(duì)象沒(méi)有指針移動(dòng)scanPtr:
allocationPtr ↓ +---+---+---+---+---+---+---+------------+ | A | B | C | E | F | G | H | | +---+---+---+---+---+---+---+------------+ ↑ scanPtr
此時(shí)scanPtr和allocationPtr重合,說(shuō)明復(fù)制結(jié)束
可以對(duì)比一下From區(qū)和To區(qū)在復(fù)制完成后的結(jié)果:
//From區(qū) +---+---+---+---+---+---+---+---+--------+ | A | B | C | D | E | F | G | H | | +---+---+---+---+---+---+---+---+--------+ //To區(qū) +---+---+---+---+---+---+---+------------+ | A | B | C | E | F | G | H | | +---+---+---+---+---+---+---+------------+
D對(duì)象沒(méi)有被復(fù)制,它將被作為垃圾進(jìn)行回收
寫(xiě)屏障如果新生代中的一個(gè)對(duì)象只有一個(gè)指向它的指針,而這個(gè)指針在老生代中,我們?nèi)绾闻袛噙@個(gè)新生代的對(duì)象是否存活?為了解決這個(gè)問(wèn)題,需要建立一個(gè)列表用來(lái)記錄所有老生代對(duì)象指向新生代對(duì)象的情況。每當(dāng)有老生代對(duì)象指向新生代對(duì)象的時(shí)候,我們就記錄下來(lái)
對(duì)象的晉升當(dāng)一個(gè)對(duì)象經(jīng)過(guò)多次新生代的清理依舊幸存,這說(shuō)明它的生存周期較長(zhǎng),也就會(huì)被移動(dòng)到老生代,這稱為對(duì)象的晉升。具體移動(dòng)的標(biāo)準(zhǔn)有兩種:
1. 對(duì)象從From空間復(fù)制到To空間時(shí),會(huì)檢查它的內(nèi)存地址來(lái)判斷這個(gè)對(duì)象是否已經(jīng)經(jīng)歷過(guò)一個(gè)新生代的清理,如果是,則復(fù)制到老生代中,否則復(fù)制到To空間中
2. 對(duì)象從From空間復(fù)制到To空間時(shí),如果To空間已經(jīng)被使用了超過(guò)25%,那么這個(gè)對(duì)象直接被復(fù)制到老生代
老生代所保存的對(duì)象大多數(shù)是生存周期很長(zhǎng)的甚至是常駐內(nèi)存的對(duì)象,而且老生代占用的內(nèi)存較多
老生代的垃圾回收算法老生代占用內(nèi)存較多(64位為1.4GB,32位為700MB),如果使用Scavenge算法,浪費(fèi)一半空間不說(shuō),復(fù)制如此大塊的內(nèi)存消耗時(shí)間將會(huì)相當(dāng)長(zhǎng)。所以Scavenge算法顯然不適合。V8在老生代中的垃圾回收策略采用Mark-Sweep和Mark-Compact相結(jié)合
Mark-Sweep(標(biāo)記清除)標(biāo)記清除分為標(biāo)記和清除兩個(gè)階段。在標(biāo)記階段需要遍歷堆中的所有對(duì)象,并標(biāo)記那些活著的對(duì)象,然后進(jìn)入清除階段。在清除階段總,只清除沒(méi)有被標(biāo)記的對(duì)象。由于標(biāo)記清除只清除死亡對(duì)象,而死亡對(duì)象在老生代中占用的比例很小,所以效率較高
標(biāo)記清除有一個(gè)問(wèn)題就是進(jìn)行一次標(biāo)記清楚后,內(nèi)存空間往往是不連續(xù)的,會(huì)出現(xiàn)很多的內(nèi)存碎片。如果后續(xù)需要分配一個(gè)需要內(nèi)存空間較多的對(duì)象時(shí),如果所有的內(nèi)存碎片都不夠用,將會(huì)使得V8無(wú)法完成這次分配,提前觸發(fā)垃圾回收。
Mark-Compact(標(biāo)記整理)標(biāo)記整理正是為了解決標(biāo)記清除所帶來(lái)的內(nèi)存碎片的問(wèn)題。標(biāo)記整理在標(biāo)記清除的基礎(chǔ)進(jìn)行修改,將其的清除階段變?yōu)榫o縮極端。在整理的過(guò)程中,將活著的對(duì)象向內(nèi)存區(qū)的一段移動(dòng),移動(dòng)完成后直接清理掉邊界外的內(nèi)存。緊縮過(guò)程涉及對(duì)象的移動(dòng),所以效率并不是太好,但是能保證不會(huì)生成內(nèi)存碎片
算法思路標(biāo)記清除和標(biāo)記整理都分為兩個(gè)階段:標(biāo)記階段、清除或緊縮階段
在標(biāo)記階段,所有堆上的活躍對(duì)象都會(huì)被標(biāo)記。每個(gè)內(nèi)存頁(yè)有一個(gè)用來(lái)標(biāo)記對(duì)象的位圖,位圖中的每一位對(duì)應(yīng)內(nèi)存頁(yè)中的一個(gè)字。這個(gè)位圖需要占據(jù)一定的空間(32位下為3.1%,64位為1.6%)。另外有兩位用來(lái)標(biāo)記對(duì)象的狀態(tài),這個(gè)狀態(tài)一共有三種(所以要兩位)——白,灰,黑:
* 如果一個(gè)對(duì)象為白對(duì)象,它還沒(méi)未被垃圾回收器發(fā)現(xiàn)
* 如果一個(gè)對(duì)象為灰對(duì)象,它已經(jīng)被垃圾回收器發(fā)現(xiàn),但其鄰接對(duì)象尚未全部處理
* 如果一個(gè)對(duì)象為黑對(duì)象,說(shuō)明他步進(jìn)被垃圾回收器發(fā)現(xiàn),其鄰接對(duì)象也全部被處理完畢了
如果將對(duì)中的對(duì)象看做由指針做邊的有向圖,標(biāo)記算法的核心就是深度優(yōu)先搜索。在初始時(shí),位圖為空,所有的對(duì)象也都是白對(duì)象。從根對(duì)象到達(dá)的對(duì)象會(huì)背染色為灰色,放入一個(gè)多帶帶的雙端隊(duì)列中。標(biāo)記階段的每次循環(huán),垃圾回收器都會(huì)從雙端隊(duì)列中取出一個(gè)對(duì)象并將其轉(zhuǎn)變?yōu)楹趯?duì)象,并將其鄰接的對(duì)象轉(zhuǎn)變?yōu)榛遥缓蟀哑溧徑訉?duì)象放入雙端隊(duì)列。如果雙端隊(duì)列為空或所有對(duì)象都變成黑對(duì)象,則結(jié)束。特別大的對(duì)象,可能會(huì)在處理時(shí)進(jìn)行分片,防止雙端隊(duì)列溢出。如果雙端隊(duì)列溢出,則對(duì)象仍然會(huì)成為灰對(duì)象,但不會(huì)被放入隊(duì)列中,這將導(dǎo)致其鄰接對(duì)象無(wú)法被轉(zhuǎn)變?yōu)榛覍?duì)象。所以在雙端隊(duì)列為空時(shí),需要掃描所有對(duì)象,如果仍有灰對(duì)象,將它們重新放入隊(duì)列中進(jìn)行處理。標(biāo)記結(jié)束后,所有的對(duì)象都應(yīng)該非黑即白,白對(duì)象將成為垃圾,等待釋放
清除和緊縮階段都是以內(nèi)存頁(yè)為單位回收內(nèi)存
清除時(shí)垃圾回收器會(huì)掃描連續(xù)存放的死對(duì)象,將其變成空閑空間,并保存到一個(gè)空閑空間的鏈表中。這個(gè)鏈表常被scavenge算法用于分配被晉升對(duì)象的內(nèi)存,但也被緊縮算法用于移動(dòng)對(duì)象
緊縮算法會(huì)嘗試將碎片頁(yè)整合到一起來(lái)釋放內(nèi)存。由于頁(yè)上的對(duì)象會(huì)被移動(dòng)到新的頁(yè)上,需要重新分配一些頁(yè)。大致過(guò)程是,對(duì)目標(biāo)碎片頁(yè)中的每個(gè)活躍對(duì)象,在空閑內(nèi)存鏈表中分配一塊內(nèi)存頁(yè),將該對(duì)象復(fù)制過(guò)去,并在碎片頁(yè)中的該對(duì)象上寫(xiě)上新的內(nèi)存地址。隨后在遷出過(guò)程中,對(duì)象的舊地址將會(huì)被記錄下來(lái),在遷出結(jié)束后,V8會(huì)遍歷所有它所記錄的舊對(duì)象的地址,將其更新為新地址。由于標(biāo)記過(guò)程中也記錄了不同頁(yè)之間的指針,這些指針在此時(shí)也會(huì)進(jìn)行更新。如果一個(gè)頁(yè)非?;钴S,如其中有過(guò)多需要記錄的指針,那么地址記錄會(huì)跳過(guò)它,等到下一輪垃圾回收進(jìn)行處理
結(jié)合使用標(biāo)記清除和標(biāo)記整理V8的老生代使用標(biāo)記清除和標(biāo)記整理結(jié)合的方式,主要采用標(biāo)記清除算法,如果空間不足以分配從新生代晉升過(guò)來(lái)的對(duì)象時(shí),才使用標(biāo)記整理
V8的優(yōu)化 Incremental Marking(增量標(biāo)記)由于全停頓會(huì)造成了瀏覽器一段時(shí)間無(wú)響應(yīng),所以V8使用了一種增量標(biāo)記的方式,將完整的標(biāo)記拆分成很多部分,每做完一部分就停下來(lái),讓JS的應(yīng)用邏輯執(zhí)行一會(huì),這樣垃圾回收與應(yīng)用邏輯交替完成。經(jīng)過(guò)增量標(biāo)記的改進(jìn)后,垃圾回收的最大停頓時(shí)間可以減少到原來(lái)的1/6左右
惰性清理由于標(biāo)記完成后,所有的對(duì)象都已經(jīng)被標(biāo)記,不是死對(duì)象就是活對(duì)象,堆上多少空間格局已經(jīng)確定。我們可以不必著急釋放那些死對(duì)象所占用的空間,而延遲清理過(guò)程的執(zhí)行。垃圾回收器可以根據(jù)需要逐一清理死對(duì)象所占用的內(nèi)存頁(yè)
其他V8后續(xù)還引入了增量式整理(incremental compaction),以及并行標(biāo)記和并行清理,通過(guò)并行利用多核CPU來(lái)提升垃圾回收的性能
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/87508.html
摘要:的內(nèi)存限制和垃圾回收機(jī)制內(nèi)存限制內(nèi)存限制一般的后端語(yǔ)言開(kāi)發(fā)中,在基本的內(nèi)存使用是沒(méi)有限制的。的內(nèi)存分代目前沒(méi)有一種垃圾自動(dòng)回收算法適用于所有場(chǎng)景,所以的內(nèi)部采用的其實(shí)是兩種垃圾回收算法。 前言 從前端思維轉(zhuǎn)變到后端, 有一個(gè)很重要的點(diǎn)就是內(nèi)存管理。以前寫(xiě)前端因?yàn)橹皇窃跒g覽器上運(yùn)行, 所以對(duì)于內(nèi)存管理一般不怎么需要上心, 但是在服務(wù)器端, 則需要斤斤計(jì)較內(nèi)存。 V8的內(nèi)存限制和垃圾回收機(jī)...
摘要:在運(yùn)行腳本時(shí),需要顯示的指定對(duì)象。大對(duì)象區(qū)每一個(gè)區(qū)域都是由一組內(nèi)存頁(yè)構(gòu)成的。這里是唯一擁有執(zhí)行權(quán)限的內(nèi)存區(qū)。換句話說(shuō),是該對(duì)象被之后所能回收到內(nèi)存的總和。一旦活躍對(duì)象已被移出,則在舊的半空間中剩下的任何死亡對(duì)象被丟棄。 內(nèi)存管理 本文以V8為背景 對(duì)之前的文章進(jìn)行重新編輯,內(nèi)容做了很多的調(diào)整,使其具有邏輯更加緊湊,內(nèi)容更加全面。 1. 基礎(chǔ)概念 1.1 生命周期 不管什么程序語(yǔ)言,內(nèi)存...
摘要:一前言的垃圾回收機(jī)制使用垃圾回收機(jī)制來(lái)自動(dòng)管理內(nèi)存。垃圾回收器只會(huì)針對(duì)新生代內(nèi)存區(qū)老生代指針區(qū)以及老生代數(shù)據(jù)區(qū)進(jìn)行垃圾回收。分別對(duì)新生代和老生代使用不同的垃圾回收算法來(lái)提升垃圾回收的效率。 V8 實(shí)現(xiàn)了準(zhǔn)確式 GC,GC 算法采用了分代式垃圾回收機(jī)制。因此,V8 將內(nèi)存(堆)分為新生代和老生代兩部分。 一、前言 V8的垃圾回收機(jī)制:JavaScript使用垃圾回收機(jī)制來(lái)自動(dòng)管理內(nèi)存。垃...
摘要:例如,和中的對(duì)象就是實(shí)現(xiàn)的對(duì)象,而對(duì)象的垃圾收集機(jī)制采用的是引用計(jì)數(shù)策略。因此,即使中的引擎使用標(biāo)記清除策略實(shí)現(xiàn),但是訪問(wèn)的對(duì)象依然是基于引用計(jì)數(shù)策略的。垃圾回收器從不移動(dòng)大對(duì)象。 Js GC原理: 找出那些不再繼續(xù)使用的變量,然后釋放其所占用的內(nèi)存,垃圾回收器會(huì)按照固定的時(shí)間間隔周期性地執(zhí)行這一操作 Js GC 策略: 標(biāo)記清除法 引用計(jì)數(shù) JavaScript 內(nèi)存分配: 在定...
摘要:引擎對(duì)堆內(nèi)存中的對(duì)象進(jìn)行分代管理新生代存活周期較短的對(duì)象,如臨時(shí)變量字符串等。內(nèi)存泄漏對(duì)于持續(xù)運(yùn)行的服務(wù)進(jìn)程,必須及時(shí)釋放不再用到的內(nèi)存。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第一期,本周的主題是調(diào)用堆棧,今天是第4天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)階計(jì)劃...
閱讀 1740·2021-11-15 11:37
閱讀 3485·2021-09-28 09:44
閱讀 1740·2021-09-07 10:15
閱讀 2860·2021-09-03 10:39
閱讀 2756·2019-08-29 13:20
閱讀 1362·2019-08-29 12:51
閱讀 2271·2019-08-26 13:44
閱讀 2187·2019-08-23 18:02