<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>

          【W(wǎng)eb技術(shù)】737- 深入理解 Chrome V8垃圾回收機(jī)制

          共 5886字,需瀏覽 12分鐘

           ·

          2020-10-07 05:51


          有很多人都聽說過V8引擎,但可能不是很了解,V8名稱叫Chrome V8,是由谷歌開源的一個(gè)高性能 JavaScript 引擎。該引擎采用 C++ 編寫,Google Chrome 瀏覽器用的就是這個(gè)引擎。V8 可以單獨(dú)運(yùn)行,也可以嵌入 C++ 應(yīng)用當(dāng)中。和其他的 JavaScript 引擎一樣,V8 會(huì)編譯、執(zhí)行 JavaScript 代碼,并一樣會(huì)管理內(nèi)存、垃圾回收等。就是因?yàn)?V8 的高性能以及跨平臺等特性,所以它也是 Node.js 的 JavaScript 引擎。

          V8引擎是前端開發(fā)者想升值加薪 必須越過的一個(gè)坎兒,因?yàn)樯婕暗叫阅埽芏嗳苏J(rèn)為沒必要,但現(xiàn)在性能卻是程序里最看重的事。作為當(dāng)下使用最廣泛的 JavaScript 引擎,V8 的生態(tài)圈非常龐大,這與它革命性的設(shè)計(jì)密不可分。

          閱讀本文之后,你可以了解到:

          • JavaScript的內(nèi)存是怎么管理的?
          • Chrome是如何進(jìn)行垃圾回收的?
          • Chrome對垃圾回收進(jìn)行了哪些優(yōu)化?

          JavaScript的內(nèi)存管理


          不管什么程序語言,內(nèi)存生命周期基本是一致的:

          1. 分配你所需要的內(nèi)存
          2. 使用分配到的內(nèi)存(讀、寫)
          3. 不需要時(shí)將其釋放歸還

          與其他需要手動(dòng)管理內(nèi)存的語言不通,在JavaScript中,當(dāng)我們創(chuàng)建變量(對象,字符串等)的時(shí)候,系統(tǒng)會(huì)自動(dòng)給對象分配對應(yīng)的內(nèi)存。

          var?n?=?123;?//?給數(shù)值變量分配內(nèi)存
          var?s?=?"azerty";?//?給字符串分配內(nèi)存

          var?o?=?{
          ??a:?1,
          ??b:?null
          };?//?給對象及其包含的值分配內(nèi)存

          //?給數(shù)組及其包含的值分配內(nèi)存(就像對象一樣)
          var?a?=?[1,?null,?"abra"];?

          function?f(a){
          ??return?a?+?2;
          }?//?給函數(shù)(可調(diào)用的對象)分配內(nèi)存

          //?函數(shù)表達(dá)式也能分配一個(gè)對象
          someElement.addEventListener('click',?function(){
          ??someElement.style.backgroundColor?=?'blue';
          },?false);

          當(dāng)系統(tǒng)發(fā)現(xiàn)這些變量不再被使用的時(shí)候,會(huì)自動(dòng)釋放(垃圾回收)這些變量的內(nèi)存,開發(fā)者不用過多的關(guān)心內(nèi)存問題。

          雖然這樣,我們開發(fā)過程中也需要了解JavaScript的內(nèi)存管理機(jī)制,這樣才能避免一些不必要的問題,比如下面代碼:

          {}=={}?//?false
          []==[]?//?false
          ''==''?//?true

          在JavaScript中,數(shù)據(jù)類型分為兩類,簡單類型和引用類型,對于簡單類型,內(nèi)存是保存在棧(stack)空間中,復(fù)雜數(shù)據(jù)類型,內(nèi)存是保存在堆(heap)空間中。

          • 基本類型:這些類型在內(nèi)存中分別占有固定大小的空間,他們的值保存在棧空間,我們通過按值來訪問的
          • 引用類型:引用類型,值大小不固定,棧內(nèi)存中存放地址指向堆內(nèi)存中的對象。是按引用訪問的。

          而對于棧的內(nèi)存空間,只保存簡單數(shù)據(jù)類型的內(nèi)存,由操作系統(tǒng)自動(dòng)分配和自動(dòng)釋放。而堆空間中的內(nèi)存,由于大小不固定,系統(tǒng)無法無法進(jìn)行自動(dòng)釋放,這個(gè)時(shí)候就需要JS引擎來手動(dòng)的釋放這些內(nèi)存。

          為什么需要垃圾回收


          在Chrome中,v8被限制了內(nèi)存的使用(64位約1.4G/1464MB , 32位約0.7G/732MB),為什么要限制呢?

          1. 表層原因是,V8最初為瀏覽器而設(shè)計(jì),不太可能遇到用大量內(nèi)存的場景
          2. 深層原因是,V8的垃圾回收機(jī)制的限制(如果清理大量的內(nèi)存垃圾是很耗時(shí)間,這樣回引起JavaScript線程暫停執(zhí)行的時(shí)間,那么性能和應(yīng)用直線下降)

          前面說到棧內(nèi)的內(nèi)存,操作系統(tǒng)會(huì)自動(dòng)進(jìn)行內(nèi)存分配和內(nèi)存釋放,而堆中的內(nèi)存,由JS引擎(如Chrome的V8)手動(dòng)進(jìn)行釋放,當(dāng)我們代碼的按照正確的寫法時(shí),會(huì)使得JS引擎的垃圾回收機(jī)制無法正確的對內(nèi)存進(jìn)行釋放(內(nèi)存泄露),從而使得瀏覽器占用的內(nèi)存不斷增加,進(jìn)而導(dǎo)致JavaScript和應(yīng)用、操作系統(tǒng)性能下降。

          Chrome 垃圾回收算法


          在JavaScript中,其實(shí)絕大多數(shù)的對象存活周期都很短,大部分在經(jīng)過一次的垃圾回收之后,內(nèi)存就會(huì)被釋放掉,而少部分的對象存活周期將會(huì)很長,一直是活躍的對象,不需要被回收。為了提高回收效率,V8 將堆分為兩類新生代和老生代,新生代中存放的是生存時(shí)間短的對象,老生代中存放的生存時(shí)間久的對象。

          新生區(qū)通常只支持 1~8M 的容量,而老生區(qū)支持的容量就大很多了。對于這兩塊區(qū)域,V8 分別使用兩個(gè)不同的垃圾回收器,以便更高效地實(shí)施垃圾回收。

          • 副垃圾回收器 - Scavenge:主要負(fù)責(zé)新生代的垃圾回收。
          • 主垃圾回收器 - Mark-Sweep & Mark-Compact:主要負(fù)責(zé)老生代的垃圾回收。

          新生代垃圾回收器 - Scavenge

          在JavaScript中,任何對象的聲明分配到的內(nèi)存,將會(huì)先被放置在新生代中,而因?yàn)榇蟛糠謱ο笤趦?nèi)存中存活的周期很短,所以需要一個(gè)效率非常高的算法。在新生代中,主要使用Scavenge算法進(jìn)行垃圾回收,Scavenge算法是一個(gè)典型的犧牲空間換取時(shí)間的復(fù)制算法,在占用空間不大的場景上非常適用。

          Scavange算法將新生代堆分為兩部分,分別叫from-space和to-space,工作方式也很簡單,就是將from-space中存活的活動(dòng)對象復(fù)制到to-space中,并將這些對象的內(nèi)存有序的排列起來,然后將from-space中的非活動(dòng)對象的內(nèi)存進(jìn)行釋放,完成之后,將from space 和to space進(jìn)行互換,這樣可以使得新生代中的這兩塊區(qū)域可以重復(fù)利用。

          簡單的描述就是:

          • 標(biāo)記活動(dòng)對象和非活動(dòng)對象
          • 復(fù)制 from space 的活動(dòng)對象到 to space 并對其進(jìn)行排序
          • 釋放 from space 中的非活動(dòng)對象的內(nèi)存
          • 將 from space 和 to space 角色互換

          那么,垃圾回收器是怎么知道哪些對象是活動(dòng)對象和非活動(dòng)對象的呢?

          有一個(gè)概念叫對象的可達(dá)性,表示從初始的根對象(window,global)的指針開始,這個(gè)根指針對象被稱為根集(root set),從這個(gè)根集向下搜索其子節(jié)點(diǎn),被搜索到的子節(jié)點(diǎn)說明該節(jié)點(diǎn)的引用對象可達(dá),并為其留下標(biāo)記,然后遞歸這個(gè)搜索的過程,直到所有子節(jié)點(diǎn)都被遍歷結(jié)束,那么沒有被標(biāo)記的對象節(jié)點(diǎn),說明該對象沒有被任何地方引用,可以證明這是一個(gè)需要被釋放內(nèi)存的對象,可以被垃圾回收器回收。

          新生代中的對象什么時(shí)候變成老生代的對象呢?

          在新生代中,還進(jìn)一步進(jìn)行了細(xì)分,分為nursery子代和intermediate子代兩個(gè)區(qū)域,一個(gè)對象第一次分配內(nèi)存時(shí)會(huì)被分配到新生代中的nursery子代,如果進(jìn)過下一次垃圾回收這個(gè)對象還存在新生代中,這時(shí)候我們移動(dòng)到 intermediate 子代,再經(jīng)過下一次垃圾回收,如果這個(gè)對象還在新生代中,副垃圾回收器會(huì)將該對象移動(dòng)到老生代中,這個(gè)移動(dòng)的過程被稱為晉升。

          老生代垃圾回收 - Mark-Sweep & Mark-Compact

          新生代空間中的對象滿足一定條件后,晉升到老生代空間中,在老生代空間中的對象都已經(jīng)至少經(jīng)歷過一次或者多次的回收所以它們的存活概率會(huì)更大,如果這個(gè)時(shí)候再使用scavenge算法的話,會(huì)出現(xiàn)兩個(gè)問題:

          • scavenge為復(fù)制算法,重復(fù)復(fù)制活動(dòng)對象會(huì)使得效率低下
          • scavenge是犧牲空間來換取時(shí)間效率的算法,而老生代支持的容量交大,會(huì)出現(xiàn)空間資源浪費(fèi)問題

          所以在老生代空間中采用了 Mark-Sweep(標(biāo)記清除) 和 Mark-Compact(標(biāo)記整理) 算法。

          Mark-Sweep

          Mark-Sweep處理時(shí)分為兩階段,標(biāo)記階段和清理階段,看起來與Scavenge類似,不同的是,Scavenge算法是復(fù)制活動(dòng)對象,而由于在老生代中活動(dòng)對象占大多數(shù),所以Mark-Sweep在標(biāo)記了活動(dòng)對象和非活動(dòng)對象之后,直接把非活動(dòng)對象清除。

          • 標(biāo)記階段:對老生代進(jìn)行第一次掃描,標(biāo)記活動(dòng)對象
          • 清理階段:對老生代進(jìn)行第二次掃描,清除未被標(biāo)記的對象,即清理非活動(dòng)對象

          看似一切 perfect,但是還遺留一個(gè)問題,被清除的對象遍布于各內(nèi)存地址,產(chǎn)生很多內(nèi)存碎片。

          Mark-Compact

          由于Mark-Sweep完成之后,老生代的內(nèi)存中產(chǎn)生了很多內(nèi)存碎片,若不清理這些內(nèi)存碎片,如果出現(xiàn)需要分配一個(gè)大對象的時(shí)候,這時(shí)所有的碎片空間都完全無法完成分配,就會(huì)提前觸發(fā)垃圾回收,而這次回收其實(shí)不是必要的。

          為了解決內(nèi)存碎片問題,Mark-Compact被提出,它是在是在 Mark-Sweep的基礎(chǔ)上演進(jìn)而來的,相比Mark-Sweep,Mark-Compact添加了活動(dòng)對象整理階段,將所有的活動(dòng)對象往一端移動(dòng),移動(dòng)完成后,直接清理掉邊界外的內(nèi)存。

          全停頓 Stop-The-World


          由于垃圾回收是在JS引擎中進(jìn)行的,而Mark-Compact算法在執(zhí)行過程中需要移動(dòng)對象,而當(dāng)活動(dòng)對象較多的時(shí)候,它的執(zhí)行速度不可能很快,為了避免JavaScript應(yīng)用邏輯和垃圾回收器的內(nèi)存資源競爭導(dǎo)致的不一致性問題,垃圾回收器會(huì)將JavaScript應(yīng)用暫停,這個(gè)過程,被稱為全停頓(stop-the-world)。

          在新生代中,由于空間小、存活對象較少、Scavenge算法執(zhí)行效率較快,所以全停頓的影響并不大。而老生代中就不一樣,如果老生代中的活動(dòng)對象較多,垃圾回收器就會(huì)暫停主線程較長的時(shí)間,使得頁面變得卡頓。

          優(yōu)化 Orinoco


          orinoco為V8的垃圾回收器的項(xiàng)目代號,為了提升用戶體驗(yàn),解決全停頓問題,它利用了增量標(biāo)記、懶性清理、并發(fā)、并行來降低主線程掛起的時(shí)間。

          增量標(biāo)記 - Incremental marking

          為了降低全堆垃圾回收的停頓時(shí)間,增量標(biāo)記將原本的標(biāo)記全堆對象拆分為一個(gè)一個(gè)任務(wù),讓其穿插在JavaScript應(yīng)用邏輯之間執(zhí)行,它允許堆的標(biāo)記時(shí)的5~10ms的停頓。增量標(biāo)記在堆的大小達(dá)到一定的閾值時(shí)啟用,啟用之后每當(dāng)一定量的內(nèi)存分配后,腳本的執(zhí)行就會(huì)停頓并進(jìn)行一次增量標(biāo)記。

          懶性清理 - Lazy sweeping

          增量標(biāo)記只是對活動(dòng)對象和非活動(dòng)對象進(jìn)行標(biāo)記,惰性清理用來真正的清理釋放內(nèi)存。當(dāng)增量標(biāo)記完成后,假如當(dāng)前的可用內(nèi)存足以讓我們快速的執(zhí)行代碼,其實(shí)我們是沒必要立即清理內(nèi)存的,可以將清理的過程延遲一下,讓JavaScript邏輯代碼先執(zhí)行,也無需一次性清理完所有非活動(dòng)對象內(nèi)存,垃圾回收器會(huì)按需逐一進(jìn)行清理,直到所有的頁都清理完畢。

          增量標(biāo)記與惰性清理的出現(xiàn),使得主線程的最大停頓時(shí)間減少了80%,讓用戶與瀏覽器交互過程變得流暢了許多,從實(shí)現(xiàn)機(jī)制上,由于每個(gè)小的增量標(biāo)價(jià)之間執(zhí)行了JavaScript代碼,堆中的對象指針可能發(fā)生了變化,需要使用寫屏障技術(shù)來記錄這些引用關(guān)系的變化,所以也暴露出來增量標(biāo)記的缺點(diǎn):

          • 并沒有減少主線程的總暫停的時(shí)間,甚至?xí)晕⒃黾?/section>
          • 由于寫屏障(Write-barrier)機(jī)制的成本,增量標(biāo)記可能會(huì)降低應(yīng)用程序的吞吐量

          并發(fā) - Concurrent

          并發(fā)式GC允許在在垃圾回收的同時(shí)不需要將主線程掛起,兩者可以同時(shí)進(jìn)行,只有在個(gè)別時(shí)候需要短暫停下來讓垃圾回收器做一些特殊的操作。但是這種方式也要面對增量回收的問題,就是在垃圾回收過程中,由于JavaScript代碼在執(zhí)行,堆中的對象的引用關(guān)系隨時(shí)可能會(huì)變化,所以也要進(jìn)行寫屏障操作。

          并行 - Parallel

          并行式GC允許主線程和輔助線程同時(shí)執(zhí)行同樣的GC工作,這樣可以讓輔助線程來分擔(dān)主線程的GC工作,使得垃圾回收所耗費(fèi)的時(shí)間等于總時(shí)間除以參與的線程數(shù)量(加上一些同步開銷)。

          V8當(dāng)前垃圾回收機(jī)制


          2011年,V8應(yīng)用了增量標(biāo)記機(jī)制。直至2018年,Chrome64和Node.js V10啟動(dòng)并發(fā)標(biāo)記(Concurrent),同時(shí)在并發(fā)的基礎(chǔ)上添加并行(Parallel)技術(shù),使得垃圾回收時(shí)間大幅度縮短。

          副垃圾回收器

          V8在新生代垃圾回收中,使用并行(parallel)機(jī)制,在整理排序階段,也就是將活動(dòng)對象從from-to復(fù)制到space-to的時(shí)候,啟用多個(gè)輔助線程,并行的進(jìn)行整理。由于多個(gè)線程競爭一個(gè)新生代的堆的內(nèi)存資源,可能出現(xiàn)有某個(gè)活動(dòng)對象被多個(gè)線程進(jìn)行復(fù)制操作的問題,為了解決這個(gè)問題,V8在第一個(gè)線程對活動(dòng)對象進(jìn)行復(fù)制并且復(fù)制完成后,都必須去維護(hù)復(fù)制這個(gè)活動(dòng)對象后的指針轉(zhuǎn)發(fā)地址,以便于其他協(xié)助線程可以找到該活動(dòng)對象后可以判斷該活動(dòng)對象是否已被復(fù)制。

          主垃圾回收器

          V8在老生代垃圾回收中,如果堆中的內(nèi)存大小超過某個(gè)閾值之后,會(huì)啟用并發(fā)(Concurrent)標(biāo)記任務(wù)。每個(gè)輔助線程都會(huì)去追蹤每個(gè)標(biāo)記到的對象的指針以及對這個(gè)對象的引用,而在JavaScript代碼執(zhí)行時(shí)候,并發(fā)標(biāo)記也在后臺的輔助進(jìn)程中進(jìn)行,當(dāng)堆中的某個(gè)對象指針被JavaScript代碼修改的時(shí)候,寫入屏障(write barriers)技術(shù)會(huì)在輔助線程在進(jìn)行并發(fā)標(biāo)記的時(shí)候進(jìn)行追蹤。

          當(dāng)并發(fā)標(biāo)記完成或者動(dòng)態(tài)分配的內(nèi)存到達(dá)極限的時(shí)候,主線程會(huì)執(zhí)行最終的快速標(biāo)記步驟,這個(gè)時(shí)候主線程會(huì)掛起,主線程會(huì)再一次的掃描根集以確保所有的對象都完成了標(biāo)記,由于輔助線程已經(jīng)標(biāo)記過活動(dòng)對象,主線程的本次掃描只是進(jìn)行check操作,確認(rèn)完成之后,某些輔助線程會(huì)進(jìn)行清理內(nèi)存操作,某些輔助進(jìn)程會(huì)進(jìn)行內(nèi)存整理操作,由于都是并發(fā)的,并不會(huì)影響主線程JavaScript代碼的執(zhí)行。

          結(jié)束


          其實(shí),大部分JavaScript開發(fā)人員并不需要考慮垃圾回收,但是了解一些垃圾回收的內(nèi)部原理,可以幫助你了解內(nèi)存的使用情況,根據(jù)內(nèi)存使用觀察是否存在內(nèi)存泄露,而防止內(nèi)存泄露,是提升應(yīng)用性能的一個(gè)重要舉措。

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計(jì)模式 重溫系列(9篇全)
          4.?正則 / 框架 / 算法等 重溫系列(16篇全)
          5.?Webpack4 入門(上)||?Webpack4 入門(下)
          6.?MobX 入門(上)?||??MobX 入門(下)
          7. 80+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點(diǎn)擊“閱讀原文”查看 80+ 篇原創(chuàng)文章

          瀏覽 39
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  日本一区二区黄色 | 欧美一区亚洲一区 | 久久日成人电影 | 亚洲无码十八禁 | 99热国产 |