V8 內(nèi)存管理(垃圾回收機(jī)制)
V8 內(nèi)存管理(垃圾回收機(jī)制)
V8 也會(huì)申請(qǐng)內(nèi)存,申請(qǐng)的內(nèi)存又會(huì)分為堆內(nèi)存和棧內(nèi)存
1.1 棧
- 棧用于存放 JS 中的基本類(lèi)型和引用類(lèi)型指針
- 棧的空間是連續(xù)的,增加刪除只需要移動(dòng)指針,操作速度非常快
- 棧的空間是有限的,當(dāng)棧滿了,就會(huì)拋出一個(gè)錯(cuò)誤
- 棧一般是在執(zhí)行函數(shù)時(shí)創(chuàng)建的,在函數(shù)執(zhí)行完畢后,棧就會(huì)被銷(xiāo)毀
1.2 堆
- 堆主要用于存儲(chǔ) JS 中的引用類(lèi)型
1.2.1 堆空間分類(lèi)
1.2.1.1 新生代(new space)
- 新生代內(nèi)存用于存放一些生命周期比較短的對(duì)象數(shù)據(jù)
1.2.1.2 老生代(old space)
- 老生代內(nèi)存用于存放一些生命周期比較長(zhǎng)的對(duì)象數(shù)據(jù)
- 當(dāng)new space的對(duì)象進(jìn)行兩個(gè)周期的垃圾回收后,如果數(shù)據(jù)還存在new space中,則將他們存放到old space中
- Old Space 使用標(biāo)記清除和標(biāo)記整理的方式進(jìn)行垃圾回收
1.2.2 什么是垃圾
- 在程序運(yùn)行過(guò)程中肯定會(huì)用到一些數(shù)據(jù),這些數(shù)據(jù)會(huì)放在堆棧中,但是在程序運(yùn)行結(jié)束后,這些數(shù)據(jù)就不會(huì)再被使用了,那些不再使用的數(shù)據(jù)就是垃圾
1.2.3 新生代的垃圾回收
- 新生代內(nèi)存有兩個(gè)區(qū)域,分別是對(duì)象區(qū)域(from) 和 空閑區(qū)域(to)
- 新生代內(nèi)存使用Scavenger 算法來(lái)管理內(nèi)存,垃圾回收的入口廣度優(yōu)先遍歷 From-Space 中的對(duì)象,從根對(duì)象出發(fā),廣度優(yōu)先遍歷所有能到達(dá)的對(duì)象,把存活的對(duì)象復(fù)制到 To-Space遍歷完成后,清空 From-SpaceFrom-Space 和 To-Space 角色互換
- 復(fù)制后的對(duì)象在 To-Space 中占用的內(nèi)存空間是連續(xù)的,不會(huì)出現(xiàn)碎片問(wèn)題
- 這種垃圾回收方式快速而又高效,但是會(huì)造成空間浪費(fèi)(有 To-Space 空閑區(qū)域)
- 新生代的 GC 比較頻繁
- 新生代的對(duì)象轉(zhuǎn)移到老生代稱(chēng)為晉升 Promote,判斷晉升的情況有兩種經(jīng)過(guò)一次 GC 還存活的對(duì)象對(duì)象復(fù)制到 To-Space 時(shí),To-Space 的空間達(dá)到一定的限制(超過(guò) 25%)
1.2.4 老生代的垃圾回收
V8 在老生代中的垃圾回收策略采用Mark-Sweep(標(biāo)記清除)和 Mark-Compact(標(biāo)記整理)相結(jié)合
1.2.4.1 Mark-Sweep(標(biāo)記清除)
- 標(biāo)記清除分為標(biāo)記和清除兩個(gè)階段
- 在標(biāo)記階段需要遍歷(深度優(yōu)先遍歷)堆中的所有對(duì)象,并標(biāo)記那些活著的對(duì)象,然后進(jìn)入清除階段。在清除階段總,只清除沒(méi)有被標(biāo)記的對(duì)象
- V8 采取的是黑色和白色來(lái)標(biāo)記數(shù)據(jù),垃圾收集之前,會(huì)把所有的數(shù)據(jù)設(shè)置為白色,用來(lái)標(biāo)記所有的尚未標(biāo)記的對(duì)象,然后會(huì)從 GC 根出發(fā),以深度優(yōu)先的方式把所有的能訪問(wèn)到的數(shù)據(jù)都標(biāo)記為黑色,遍歷結(jié)束后黑色的就是活的數(shù)據(jù),白色的就是可以清理的垃圾數(shù)據(jù)
- 由于標(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ì)出現(xiàn)內(nèi)存溢出的問(wèn)題
1.2.4.2 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)存碎片,一般 10 次標(biāo)記清理會(huì)伴隨一次標(biāo)記整理
1.2.5 優(yōu)化
- 在執(zhí)行垃圾回收算法期間,JS 腳本需要暫停,這種叫 Stop the world(全停頓)
- 如果回收時(shí)間過(guò)長(zhǎng),會(huì)引起卡頓
- 性能優(yōu)化把大任務(wù)拆分小任務(wù),分步執(zhí)行,類(lèi)似 fiber將一些任務(wù)放在后臺(tái)執(zhí)行,不占用主線程
1.2.5.1 Parallel(并行執(zhí)行)
- 新生代的垃圾回收采取并行策略提升垃圾回收速度,它會(huì)開(kāi)啟多個(gè)輔助線程來(lái)執(zhí)行新生代的垃圾回收工作
1.2.5.2 增量標(biāo)記
- 老生代因?yàn)閷?duì)象又大又多,所以垃圾回收的時(shí)間更長(zhǎng),采用增量標(biāo)記的方式進(jìn)行優(yōu)化
- 增量標(biāo)記就是把標(biāo)記工作分成多個(gè)階段,每個(gè)階段都只標(biāo)記一部分對(duì)象,和主線程的執(zhí)行穿插進(jìn)行
- 為了支持增量標(biāo)記,V8 必須可以支持垃圾回收的暫停和恢復(fù),所以采用了黑白灰三色標(biāo)記法黑色表示這個(gè)節(jié)點(diǎn)被 GC 根引用到了,而且該節(jié)點(diǎn)的子節(jié)點(diǎn)都已經(jīng)標(biāo)記完成了灰色表示這個(gè)節(jié)點(diǎn)被 GC 根引用到了,但子節(jié)點(diǎn)還沒(méi)被垃圾回收器標(biāo)記處理,也表明目前正在處理這個(gè)節(jié)點(diǎn)白色表示此節(jié)點(diǎn)還沒(méi)未被垃圾回收器發(fā)現(xiàn),如果在本輪遍歷結(jié)束時(shí)還是白色,那么這塊數(shù)據(jù)就會(huì)被收回
- 引入了灰色標(biāo)記后,就可以通過(guò)判斷有沒(méi)有灰色節(jié)點(diǎn)來(lái)判斷標(biāo)記是否完成了,如果有灰色節(jié)點(diǎn),下次恢復(fù)的應(yīng)該從灰色節(jié)點(diǎn)繼續(xù)執(zhí)行
1.2.5.3 Write-barrier(寫(xiě)屏障)
- 當(dāng)黑色指向白色節(jié)點(diǎn)的時(shí)候,就會(huì)觸發(fā)寫(xiě)屏障,這個(gè)寫(xiě)屏障會(huì)把白色節(jié)點(diǎn)設(shè)置為灰色
1.2.5.4 Lazy Sweeping(惰性清理)
- 當(dāng)增量標(biāo)記完成后,如果內(nèi)存夠用,先不清理,等 JS 代碼執(zhí)行完慢慢清理
1.2.5.5 concurrent(并發(fā)回收)
- 其實(shí)增量標(biāo)記和惰性清理并沒(méi)有減少暫停的總時(shí)間
- 并發(fā)回收就是主線程在執(zhí)行過(guò)程中,輔助線程可以在后臺(tái)完成垃圾回收工作
- 標(biāo)記操作全都由輔助線程完,清理操作由主線程和輔助線程配合完成
文章出自:??前端餐廳??,如有轉(zhuǎn)載本文請(qǐng)聯(lián)系前端餐廳ReTech今日頭條號(hào)。
github:??https://github.com/zuopf769??