亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

V8的內存管理與垃圾回收算法是什么

發布時間:2022-04-28 10:15:39 來源:億速云 閱讀:111 作者:zzz 欄目:web開發

今天小編給大家分享一下V8的內存管理與垃圾回收算法是什么的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

V8的內存管理與垃圾回收算法是什么

V8的內存限制與解決辦法

V8最初為瀏覽器設計,遇到大內存使用的場景較少,在設計上默認對內存使用存在限制,只允許使用部分內存,64位系統可允許使用內存約1.4g,32位系統約0.7g。如下代碼所示,在Node中查看所依賴的V8引擎的內存限制方法:

process.memoryUsage();

// 返回內存的使用量,單位字節
{
  rss: 22953984,
  // 申請的總的堆內存
  heapTotal: 9682944,
  // 已使用的堆內存
  heapUsed: 5290344,
  external: 9388
}

V8的內存管理與垃圾回收算法是什么

V8限制內存使用大小還有另一個重要原因,堆內存過大時V8執行垃圾回收的時間較久(1.5g50ms),做非增量式的垃圾回收要更久(1.5g1s)。在后續講解了V8的垃圾回收機制后相信大家更能感同身受。

雖然V8引擎對內存使用做了限制,但是同樣暴露修改內存限制的方法,就是啟動V8引擎時添加相關參數,下面代碼演示在Node中修改依賴的V8引擎內存限制:

# 更改老生代的內存限制,單位mb
node --max-old-space-size=2048 index.js

# 更改新生代的內存限制,單位mb
node --max-semi-space-size=1024=64 index.js

這里需要注意的是更改的新生代的內存的語法已經更改為上述的寫法,且單位也由kb變成了mb,舊的寫法是node --max-new-space-size,可以通過下面命令查詢當前Node環境修改新生代內存的語法:

node --v8-options | grep max

V8的內存管理與垃圾回收算法是什么

V8垃圾回收策略

在引擎的垃圾自動回收機制的歷史演變中,人們發現是沒有一種通用的可以解決任何場景下垃圾回收的算法的。因此現代垃圾回收算法根據對象的存活時間將內存垃圾進行分代分代垃圾回收算法就是對不同類別的內存垃圾實行不同的回收算法。

V8將內存分為新生代老生代兩種:

  • 新生代內存中的對象存活時間較短

  • 老生代內存中代對象存活時間較長或是常駐內存

新生代內存存放在新生代內存空間(semispace)中,老生代內存存放在老生代內存空間中(oldspace),如下圖所示:

V8的內存管理與垃圾回收算法是什么

  • 新生代內存采用Scavenge算法

  • 老生代內存采用Mark-SweepMark-Compact算法

下面我們看看Scavenge的算法邏輯吧!

Scavenge算法

對于新生代內存的內存回收采用Scavenge算法,Scavenge的具體實現采用的是Cheney算法。Cheney算法是將新生代內存空間一分為二,一個空間處于使用狀態(FromSpace),一個空間處于空閑狀態(稱為ToSpace)。

V8的內存管理與垃圾回收算法是什么

在內存開始分配時,首先在FromSpace中進行分配,垃圾回收機制執行時會檢查FromSpace中的存活對象,存活對象會被會被復制到ToSpace,非存活對象所占用的空間將被釋放,復制完成后FromSpaceToSpace的角色將翻轉。當一個對象多次復制后依然處于存活狀態,則認為其是長期存活對象,此時將發生晉升,然后該對象被移動到老生代空間oldSpace中,采用新的算法進行管理。

V8的內存管理與垃圾回收算法是什么

Scavenge算法其實就是在兩個空間內來回復制存活對象,是典型的空間換時間做法,所以非常適合新生代內存,因為僅復制存活的對象且新生代內存中存活對象是占少數的。但是有如下幾個重要問題需要考慮:

  • 引用避免重復拷貝

假設存在三個對象temp1、temp2、temp3,其中temp2、temp3都引用了temp1,js代碼示例如下:

var temp2 = {
  ref: temp1,
}

var temp3 = {
  ref: temp1,
}

var temp1 = {}

FromSpace中拷貝temp2ToSpace中時,發現引用了temp1,便把temp1也拷貝到ToSpace,是一個遞歸的過程。但是在拷貝temp3時發現也引用了temp1,此時再把temp1拷貝過去則重復了。

要避免重復拷貝,做法是拷貝時給對象添加一個標記visited表示該節點已被訪問過,后續通過visited屬性判斷是否拷貝對象。

  • 拷貝后保持正確的引用關系

還是上述引用關系,由于temp1不需要重復拷貝,temp3被拷貝到ToSpace之后不知道temp1對象在ToSpace中的內存地址。

做法是temp1被拷貝過去后該對象節點上會生成新的field屬性指向新的內存空間地址,同時更新到舊內存對象的forwarding屬性上,因此temp3就可以通過舊temp1forwarding屬性找到在ToSpace中的引用地址了。

內存對象同時存在于新生代和老生代之后,也帶來了問題:

  • 內存對象跨代(跨空間)后如何標記

const temp1 = {}

const temp2 = {
  ref: temp1,
}

比如上述代碼中的兩個對象temp1temp2都存在于新生代,其中temp2引用了temp1。假設在經過GC之后temp2晉升到了老生代,那么在下次GC的標記階段,如何判斷temp1是否是存活對象呢?

在基于可達性分析算法中要知道temp1是否存活,就必須要知道是否有根對象引用引用了temp1對象。如此的話,年輕代的GC就要遍歷所有的老生代對象判斷是否有根引用對象引用了temp1對象,如此的話分代算法就沒有意義了。

解決版本就是維護一個記錄所有的跨代引用的記錄集,它是寫緩沖區的一個列表。只要有老生代中的內存對象指向了新生代內存對象時,就將老生代中該對象的內存引用記錄到記錄集中。由于這種情況一般發生在對象寫的操作,顧稱此為寫屏障,還一種可能的情況就是發生在晉升時。記錄集的維護只要關心對象的寫操作和晉升操作即可。此是又帶來了另一個問題:

  • 每次寫操作時維護記錄集的額外開銷

優化的手段是在一些Crankshaft操作中是不需要寫屏障的,還有就是棧上內存對象的寫操作是不需要寫屏障的。還有一些,更多的手段就不在這里過多討論。

  • 緩解Scavenge算法內存利用率不高問題

新生代內存中存活對象占比是相對較小的,因此可以在分配空間時,ToSpace可以分配的小一些。做法是將ToSpace空間分成S0S1兩部分,S0用作于ToSpaceS1與原FromSpace合并當成FromSpace

V8的內存管理與垃圾回收算法是什么

Scavenge算法中深度/廣度優先的區別

垃圾回收算法中,識別內存對象是否是垃圾的機制一般有兩種:引用計數基于可達性分析

基于可達性分析,就是找出所有的根引用(比如全局變量等),遍歷所有根引用,遞歸根引用上的所有引用,凡是被遍歷到的都是存活對象并打上標記,此時空間中的其他內存對象都是死對象,由此構建了一個有向圖

考慮到遞歸的限制問題,遞歸邏輯一般采用非遞歸實現,常見的有廣度優先和深度優先算法。兩者的區別在于:

  • 深度優先拷貝到ToSpace時改變了內存對象的排列順序,使得有引用關系的對象距離較近。原因是拷貝完自己之后直接拷貝自己引用的對象,因此相關的對象便在ToSpace中靠的較近

  • 深度優先正好相反

因為CPU的緩存策略,會在讀取內存對象時有很大概率把他后面的對象一起讀,目的是為了更快的命中緩存。因為在代碼開發期間很常見的場景就是obj1.obj2.obj3,此時CPU讀取obj1時如果把后面的obj2obj3一起讀的話,則很利于命中緩存。

所以深度優先的算法更利于業務邏輯命中緩存,但是其實現需要依賴額外的棧輔助實現算法,對內存空間有消耗。廣度優先則相反,無法提升緩存命中,但是其實現可以利用指針巧妙的避開空間消耗,算法的執行效率高。

新生代內存對象的晉升條件

新生代中的內存對象如果想晉升到老生代需要滿足如下幾個條件:

  • 對象是否經歷過Scavenge回收

  • ToSpace的內存使用占比不能超過限制

判斷是否經歷過Scavenge的GC的邏輯是,每次GC時給存活對象的age屬性+1,當再次GC的時候判斷age屬性即可。基本的晉升示意圖如下所示:

V8的內存管理與垃圾回收算法是什么

老生代內存中,長期存活的對象較多,無法采取Scavenge算法回收的原因在于:

  • 存活對象較多導致復制效率低下

  • 浪費了一半的內存空間

老生代內存對象的回收算法

老生代內存空間的垃圾回收采用的是標記清除Mark-Sweep)和標記整理Mark-Compact)結合的方式。標記清除分為兩部分:

  • 標記階段

  • 清除階段(如果是標記整理則是整理階段)

在標記階段遍歷老生代堆內存中的所有內存對象,并對活著的對象做標記,清除階段只清理未被標記的對象。原因是:老生代內存中非存活對象占少數。

V8的內存管理與垃圾回收算法是什么

如上圖所示,標記清除存在的一個問題是清理之后存在了不連續的空間導致無法繼續利用,所以對于老生代內存空間的內存清理需要結合標記整理的方案。該方案是在標記過程中將活著的對象往一側移動,移動完成后再清理界外的所有非存活對象移除。

V8的內存管理與垃圾回收算法是什么

垃圾回收的全暫停

垃圾回收時需要暫停應用執行邏輯,待垃圾回收機制結束后再恢復應用執行邏輯,該行為稱為“全暫停”,也就是常說的Stop The World,簡稱STW。對新生代內存的垃圾回收該行為對應用執行影響不大,但是老生代內存由于存活對象較多,所以老生代內存的垃圾回收造成的全停頓影響非常大。

V8的內存管理與垃圾回收算法是什么

V8為了優化GC的全暫停時間,還引入了增量標記并發標記并行標記增量整理并行清理延遲清理等方式。

STW優化

衡量垃圾回收所用時間的一個重要指標是執行 GC 時主線程暫停的時間量。STW所帶來的影響是無法接受的,因此V8也采取的很多優化手段。

  • 并行GC

GC的過程需要做大量的事情從而在主線程上導致STW現象,并行GC的做法是開多個輔助線程分擔GC的事情。該做法依然無法避免STW現象的,但是可以減少STW的總時間,取決于開啟的輔助線程數量。

V8的內存管理與垃圾回收算法是什么

  • 增量GC

增量GC將GC工作進行拆分,并在主線程中間歇的分步執行。該做法并不會減少GC的時間,相反會稍微花銷,但是它同樣會減少GC的STW的總時間。

V8的內存管理與垃圾回收算法是什么

  • 并發GC

并發GC是指GC在后臺運行,不再在主線程運行。該做法會避免STW現象。

V8的內存管理與垃圾回收算法是什么

  • 空閑時間GC

Chrome中動畫的渲染大約是60幀(每幀約16ms),如果當前渲染所花費時間每達到16.6ms,此時則有空閑時間做其他事情,比如部分GC任務。

V8的內存管理與垃圾回收算法是什么

減少垃圾回收的影響

想要提高執行效率要盡量減少垃圾回收的執行和消耗:

  • 慎把內存當作緩存,小心把對象當作緩存,要合理限制過期時間和無限增長的問題,可以采用lru策略

  • Node中避免使用內存存儲用戶會話,否則在內存中存放大量用戶會話對象導致老生代內存激增,影響清理性能進而影響應用執行性能和內存溢出。改進方式使用使用redis等。將緩存轉移到外部的好處:

    • 減少常駐內存對象的數量,垃圾回收更高效

    • 進程之間可以共享緩存

以上就是“V8的內存管理與垃圾回收算法是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

v8
AI

海阳市| 巴林左旗| 启东市| 麟游县| 阳朔县| 金昌市| 蒙山县| 新巴尔虎左旗| 临潭县| 苍南县| 谷城县| 枣阳市| 武汉市| 永宁县| 铜陵市| 视频| 上蔡县| 保康县| 交口县| 英德市| 甘肃省| 金门县| 京山县| 罗甸县| 饶阳县| 北宁市| 赣榆县| 沁源县| 海城市| 金塔县| 道真| 金昌市| 民和| 梅河口市| 磐安县| 澎湖县| 夏邑县| 高州市| 广西| 永宁县| 安阳市|