<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          高性能 JavaScript 引擎 V8 - 垃圾回收

          共 2780字,需瀏覽 6分鐘

           ·

          2021-03-12 10:33

          陳錦紅,微醫(yī)前端技術(shù)部前端工程師,一位喜歡與大自然接觸的女孩!

          JavaScript 是一門具有自動(dòng)垃圾收集機(jī)制的編程語言,由執(zhí)行環(huán)境負(fù)責(zé)在代碼執(zhí)行時(shí)管理內(nèi)存,當(dāng)運(yùn)行 javascript 時(shí),你需要一個(gè)引擎來處理它,無論是在瀏覽器還是在 node.js 環(huán)境中, V8是你的選擇之一,也一定是你的不二選擇。下面我們就一起來看看V8是怎么進(jìn)行高效的垃圾回收的吧。

          垃圾數(shù)據(jù)是怎么產(chǎn)生的

          • 首先,我們來看看垃圾數(shù)據(jù)是怎么產(chǎn)生的?看如下代碼:
           let test = new Object()
           test.a = new Array(10)

          當(dāng) JavaScript 執(zhí)行這段代碼的時(shí)候,會(huì)先在全局作用域中添加一個(gè) test 變量,并在堆中創(chuàng)建了一個(gè)空對(duì)象,將該對(duì)象的地址指向了 test。

          隨后又創(chuàng)建一個(gè)大小為 10 的數(shù)組,并將屬性地址指向了 test.a。此時(shí)的內(nèi)存布局圖如下所示:

          如果此時(shí),我將另外一個(gè)對(duì)象賦給了 a 屬性,代碼如下所示:

          test.a = new Object()

          那么此時(shí)的內(nèi)存布局如下所示:

          我們可以看到,a 屬性之前是指向堆中數(shù)組對(duì)象的,現(xiàn)在已經(jīng)指向了另外一個(gè)空對(duì)象,那么此時(shí)堆中的數(shù)組對(duì)象就成為了垃圾數(shù)據(jù),因?yàn)槲覀儫o法從一個(gè)根對(duì)象遍歷到這個(gè) Array 對(duì)象,那么此時(shí)數(shù)組對(duì)象就成了垃圾數(shù)據(jù)。

          垃圾回收算法

          垃圾回收的實(shí)現(xiàn)簡(jiǎn)單分為以下三個(gè)步驟:

          第一步:可訪問性

          從 GC Roots 對(duì)象出發(fā),遍歷 GC Root 中的所有對(duì)象:

          • 可訪問對(duì)象:通過 GC Root 遍歷到的對(duì)象,我們就認(rèn)為該對(duì)象是可訪問的(reachable),那么必須保證這些對(duì)象應(yīng)該在內(nèi)存中保留。
          • 不可訪問對(duì)象:通過 GC Roots 沒有遍歷到的對(duì)象,則是不可訪問的(unreachable),并會(huì)對(duì)其做上標(biāo)記,那么這些不可訪問的對(duì)象就可能被回收。

          瀏覽器環(huán)境中,GC Root 有很多,通常包括了以下幾種 (但是不止于這幾種):全局的 window 對(duì)象(位于每個(gè) iframe 中);文檔 DOM 樹,由可以通過遍歷文檔到達(dá)的所有原生 DOM 節(jié)點(diǎn)組成;存放棧上變量。

          第二步:回收不可訪問對(duì)象所占據(jù)的內(nèi)存

          • 其實(shí)就是在所有的標(biāo)記完成之后,統(tǒng)一清理內(nèi)存中所有被標(biāo)記為可回收的對(duì)象。

          第三步:內(nèi)存整理

          • 頻繁回收對(duì)象后,內(nèi)存中就會(huì)存在大量不連續(xù)空間,稱為內(nèi)存碎片。當(dāng)出現(xiàn)了大量的內(nèi)存碎片之后,如果需要分配較大的連續(xù)內(nèi)存時(shí),就會(huì)出現(xiàn)內(nèi)存不足的情況,所以最后一步需要整理這些內(nèi)存碎片。但這步不是必須的,比如接下來我們要介紹的副垃圾回收器就不會(huì)產(chǎn)生內(nèi)存碎片。

          以上就是大致的垃圾回收的流程。目前 V8 采用了兩個(gè)垃圾回收器,主垃圾回收器和副垃圾回收器,下面我們?cè)倬唧w來看看兩個(gè)回收器是怎么回收垃圾的。

          副垃圾回收器和主垃圾回收器

          • 在 V8 中,會(huì)把堆分為新生代(新生代通常只支持 1~8M 的容量)和老生代(容量大)兩個(gè)區(qū)域,新生代中存放的是生存時(shí)間短的對(duì)象,老生代中存放生存時(shí)間久的對(duì)象。

          副垃圾回收器

          • 負(fù)責(zé)新生代的垃圾回收,大多數(shù)小的對(duì)象都會(huì)被分配到新生代,垃圾回收比較頻繁。

          • 新生代中的垃圾數(shù)據(jù)用 Scavenge 算法來處理。分為兩個(gè)區(qū)域:對(duì)象區(qū)域 ,空閑區(qū)域。如下圖所示:

          • 垃圾回收過程:

            新加入的對(duì)象都會(huì)存放到對(duì)象區(qū)域,當(dāng)對(duì)象區(qū)域快被寫滿時(shí),就需要執(zhí)行一次垃圾清理操作。

            • 1.垃圾標(biāo)記和清理:首先要對(duì)對(duì)象區(qū)域中的垃圾做標(biāo)記;標(biāo)記完成之后,就進(jìn)入垃圾清理階段,如下圖:

              圖中可以看到,副垃圾回收器會(huì)把這些我們?nèi)匀辉谟玫膶?duì)象復(fù)制到空閑區(qū)域中,同時(shí)它還會(huì)把這些對(duì)象有序地排列起來,在復(fù)制過程,相當(dāng)于完成了內(nèi)存整理操作,復(fù)制后空閑區(qū)域就沒有內(nèi)存碎片了。

            • 2.角色翻轉(zhuǎn):完成復(fù)制后,進(jìn)行角色翻轉(zhuǎn)。把原來的對(duì)象區(qū)變成空閑區(qū),把原來的空閑區(qū)變成對(duì)象區(qū),如下圖:

          主垃圾回收器

          • 負(fù)責(zé)老生代中的垃圾回收,大多數(shù)占用空間大、存活時(shí)間長(zhǎng)的對(duì)象都會(huì)被分配到老生代里。

          • 老生代中的垃圾數(shù)據(jù)用——標(biāo)記 - 清除算法進(jìn)行垃圾回收,因?yàn)槔仙械膶?duì)象通常比較大,復(fù)制大對(duì)象非常耗時(shí),會(huì)導(dǎo)致回收?qǐng)?zhí)行效率不高,所以采用標(biāo)記清除法。

          • 垃圾回收過程:

            • 1.標(biāo)記:標(biāo)記階段就是從一組根元素開始,遞歸遍歷這組根元素,在這個(gè)遍歷過程中,能到達(dá)的元素稱為活動(dòng)對(duì)象,沒有到達(dá)的元素就可以判斷為垃圾數(shù)據(jù)。

            • 2.清除:它和副垃圾回收器的垃圾清除過程完全不同,主垃圾回收器會(huì)直接將標(biāo)記為垃圾的數(shù)據(jù)清理掉,如下圖:

            • 3.整理:從上圖可以看到,清除后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,過多的碎片會(huì)導(dǎo)致大對(duì)象無法分配到足夠的連續(xù)內(nèi)存,于是需要引進(jìn)另一種算法——標(biāo)記 - 整理,整理過程如下圖:

          優(yōu)化垃圾回收器

          • 由于 JavaScript 是運(yùn)行在主線程之上的,在垃圾回收時(shí)會(huì)阻塞 JavaScript 腳本的執(zhí)行,會(huì)造成頁(yè)面卡頓等問題,使得用戶體驗(yàn)不佳。

          • 為了解決上述問題,V8 團(tuán)隊(duì)推出了并行、并發(fā)和增量等垃圾回收技術(shù),這些技術(shù)主要是從兩方面來解決垃圾回收效率問題的:

            • 1.將一個(gè)完整的垃圾回收的任務(wù)拆分成多個(gè)小的任務(wù),解決單個(gè)垃圾回收時(shí)間長(zhǎng)的問題。
            • 2.將標(biāo)記對(duì)象、移動(dòng)對(duì)象等任務(wù)轉(zhuǎn)移到后臺(tái)線程進(jìn)行,減少主阻塞線程的時(shí)間。

          接下來我們一起來看下具體這幾種技術(shù)是怎么優(yōu)化的。

          并行回收

          • 如果只有一個(gè)主線程進(jìn)行垃圾回收,會(huì)造成停頓時(shí)間過長(zhǎng)。所以 V8 團(tuán)隊(duì)推出主線程在執(zhí)行垃圾回收的任務(wù)時(shí),引入多個(gè)輔助線程來并行處理,這樣就會(huì)加速垃圾回收的執(zhí)行速度,如下圖:

          • 副垃圾回收器所采用的就是并行策略,它在執(zhí)行垃圾回收的過程中,啟動(dòng)了多個(gè)線程來負(fù)責(zé)新生代中的垃圾清理操作,這些線程同時(shí)將對(duì)象空間中的數(shù)據(jù)移動(dòng)到空閑區(qū)域。由于數(shù)據(jù)的地址發(fā)生了改變,所以還需要同步更新引用這些對(duì)象的指針。

          增量回收

          • 并行回收雖然能增加垃圾回收效率,但是還是一種阻塞的方式進(jìn)行垃圾回收,試想下如果老生代中存在一個(gè)很大的對(duì)象,還是會(huì)造成一個(gè)長(zhǎng)時(shí)間暫停。

          • 增量回收采用將標(biāo)記工作把垃圾回收工作分解為更小的塊,每次只進(jìn)行小部分垃圾回收,減少主線程阻塞時(shí)間,如下圖:

          并發(fā)回收

          • 雖然增量回收已經(jīng)能大大降低我們主線程阻塞的時(shí)間,但是所有的標(biāo)記和清除還是在主線程上。那有沒有辦法可以在不阻塞主線程情況下執(zhí)行呢?也由此 V8 推出了并發(fā)回收。

          • 并發(fā)回收,是指主線程在執(zhí)行 JavaScript 的過程中,輔助線程能夠在后臺(tái)完成執(zhí)行垃圾回收的操作,如下圖:

          在實(shí)際的應(yīng)用中,這三種回收機(jī)制通常是融合在一起用的。

          參考資料

          • 圖解 Google V8

          瀏覽 64
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天爽夜夜爽人人爽 | 一区二区三区入口 | 一级黄色免费视屏 | 天天操夜 | 国产操逼网络 |