深入理解java虛擬機—— 虛擬機 新型垃圾收集器 Shenandoah GC, ZGC

目錄

一、垃圾回收器的發展歷程簡介:

二、衡量垃圾收集器的三項最重要的指標

1、計算機的發展趨勢以及垃圾回收器的選擇

Shenandoah相比起G1又有什麼改進?

Shenandoah收集器的工作過程分爲以下九個階段:

Shenandoah收集器的工作過程:

Brooks Pointer

Shenandoah在實際應用中的性能表現

二、ZGC收集器

ZGC的功能實現:

染色指針的三大優勢:

ZGC收集器是如何工作

處理過程:


一、垃圾回收器的發展歷程簡介:

HotSpot的垃圾收集器從Serial發展到CMS再到G1,經歷了逾二十年時間,經過了數百上千萬臺服務器上的應用實踐,已經被淬鍊得相當成熟了,不過它們距離“完美”還是很遙遠。

 

二、衡量垃圾收集器的三項最重要的指標

1、內存佔用(Footprint)

2、吞吐量(Throughput)

3、延遲(Latency)

 

三者共同構成了一個“不可能三角(三元悖論)”。三者總體的表現會隨技術進步而越來越好,但是要在這三個方面同時具有卓越表現的“完美”收集器是極其困難甚至是不可能的,一款優秀的收集器通常最多可以同時達成其中的兩項

 

番外拓展:

看到這個三元悖論頓時是不是感覺有點熟悉 

分佈式系統有三個指標。 

  • Consistency
  • Availability
  • Partition tolerance

   CAP 定理的含義

 

1、計算機的發展趨勢以及垃圾回收器的選擇

(1)、內存:在內存佔用、吞吐量和延遲這三項指標裏,延遲的重要性日益凸顯,越發備受關注。其原因是隨着計算機硬件的發展、性能的提升,我們越來越能容忍收集器多佔用一點點內存;

(2)、吞吐量:硬件性能增長,對軟件系統的處理能力是有直接助益的,硬件的規格和性能越高,也有助於降低收集器運行時對應用程序的影響,換句話說,吞吐量會更高。

(3)、延遲:對延遲則不是這樣,硬件規格提升,準確地說是內存的擴大,對延遲反而會帶來負面的效果,這點也是很符合直觀思維的:虛擬機要回收完整的1TB的堆內存,毫無疑問要比回收1GB的堆內存耗費更多時間。

 

 

黃色 階段表示必須掛起用戶線程,綠色表示收集器線程與用戶線程是併發工作的。由圖可見,在CMS和G1之前的全部收集器,其工作的所有步驟都會產生“Stop The World”式的停頓;CMS和G1分別使用增量更新和原始快照技術,實現了標記階段的併發,不會因管理的堆內存變大,要標記的對象變多而導致停頓時間隨之增長。但是對於標記階段之後的處理,仍未得到妥善解決。CMS使用標記-清除算法,雖然避免了整理階段收集器帶來的停頓,但是清除算法不論如何優化改進,在設計原理上避免不了空間碎片的產生,隨着空間碎片不斷淤積最終依然逃不過“Stop The World”的命運。G1雖然可以按更小的粒度進行回收,從而抑制整理階段出現時間過長的停頓,但畢竟也還是要暫停的。

最後的兩款收集器,Shenandoah和ZGC,幾乎整個工作過程全部都是併發的,只有初始標記、最終標記這些階段有短暫的停頓,這部分停頓的時間基本上是固定的,與堆的容量、堆中對象的數量沒有正比例關係。實際上,它們都可以在任意可管理的(譬如現在ZGC只能管理4TB以內的堆)堆容量下,實現垃圾收集的停頓都不超過十毫秒這種以前聽起來是天方夜譚、匪夷所思的目標。這兩款目前仍處於實驗狀態的收集器,被官方命名爲“低延遲垃圾收集器”(Low-LatencyGarbage Collector或者Low-Pause-Time Garbage Collector)。

 

 

Shenandoah相比起G1又有什麼改進?

Shenandoah 與 G1 對比:

相同:

Shenandoah也是使用基於Region的堆內存佈局,同樣有着用於存放大對象的Humongous Region,默認的回收策略也同樣是優先處理回收價值最大的Region

不同:

管理堆內存方面,與G1至少有三個明顯的不同之處:

1、Shenandoah 支持併發的整理算法;G1支持並行整理算法。

2、Shenandoah(目前)是默認不使用分代收集的;G1 有專門的新生代Region或者老年代Region的存在;

3、Shenandoah摒棄了在G1中耗費大量內存和計算資源去維護的記憶集,改用名爲“連接矩陣”(Connection Matrix)的全局數據結構來記錄跨Region的引用關係,降低了處理跨代指針時的記憶集維護消耗,也降低了僞共享問題的發生概率。

可參考:G1回收器每一份Region都必須有一份卡表,這導致G1的記憶集(和其他內存消耗)可能會佔整個堆容量的20%乃至更多的內存空間;           

 詳細參考《 虛擬機垃圾收集器原理

連接矩陣可以簡單理解爲一張二維表格,如果Region N有對象指向RegionM,就在表格的N行M列中打上一個標記,如圖3-15所示,如果Region 5中的對象Baz引用了Region 3的Foo,Foo又引用了Region 1的Bar,那連接矩陣中的5行3列、3行1列就應該被打上標記。在回收時通過這張表格就可以得出哪些Region之間產生了跨代引用。

 

Shenandoah收集器的工作過程分爲以下九個階段:

(1)、初始標記(Initial Marking)

與G1一樣,首先標記與GC Roots直接關聯的對象,這個階段仍是“Stop The World”的,但停頓時間與堆大小無關,只與GC Roots的數量相關。

(2)、併發標記(Concurrent Marking):

與G1一樣,遍歷對象圖,標記出全部可達的對象,這個階段是與用戶線程一起併發的,時間長短取決於堆中存活對象的數量以及對象圖的結構複雜程度。

(3)、最終標記(Final Marking):

與G1一樣,處理剩餘的SATB掃描,並在這個階段統計出回收價值最高的Region,將這些Region構成一組回收集(Collection Set)。最終標記階段也會有一小段短暫的停頓。

(4)、併發清理(Concurrent Cleanup):

這個階段用於清理那些整個區域內連一個存活對象都沒有找到的Region(這類Region被稱爲Immediate Garbage Region)。

(5)、併發回收(Concurrent Evacuation):

併發回收階段是Shenandoah與之前HotSpot中 與其他收集器的核心差異 。在這個階段,Shenandoah要把回收集裏面的存活對象先複製一份到其他未被使用的Region之中。複製對象這件事情如果將用戶線程凍結起來再做那是相當簡單的,但如果兩者必須要同時併發進行的話,就變得複雜起來了。其困難點是 在移動對象的同時,用戶線程仍然可能不停對被移動的對象進行讀寫訪問,移動對象是一次性的行爲,但移動之後整個內存中所有指向該對象的引用都還是舊對象的地址,這是很難一瞬間全部改變過來的。對於併發回收階段遇到的這些困難,Shenandoah將會通過 讀屏障和被稱爲“Brooks Pointers”的轉發指針來解決。併發回收階段運行的時間長短取決於回收集的大小。

(6)、初始引用更新(Initial Update Reference):

併發回收階段複製對象結束後,還需要把堆中所有指向舊對象的引用修正到複製後的新地址,這個操作稱爲引用更新。引用更新的初始化階段實際上並未做什麼具體的處理,設立這個階段只是爲了建立一個線程集合點,確保所有併發回收階段中進行的收集器線程都已完成分配給它們的對象移動任務而已。初始引用更新時間很短,會產生一個非常短暫的停頓

(7)、併發引用更新(Concurrent Update Reference):

真正開始進行引用更新操作,這個階段是與用戶線程一起併發的,時間長短取決於內存中涉及的引用數量的多少。併發引用更新與併發標記不同,它不再需要沿着對象圖來搜索,只需要按照內存物理地址的順序,線性地搜索出引用類型,把舊值改爲新值即可。

(8)、最終引用更新(Final Update Reference):

解決了堆中的引用更新後,還要修正存在於GC Roots中的引用。這個階段是Shenandoah的最後一次停頓,停頓時間只與GCRoots的數量相關。

(9)、併發清理(Concurrent Cleanup):

經過併發回收和引用更新之後,整個回收集中所有的Region已再無存活對象,這些Region都變成Immediate Garbage Regions了,最後再調用一次併發清理過程來回收這些Region的內存空間,供以後新對象分配使用。

 

重點

其中三個最重要的併發階段(併發標記、併發回收、併發引用更新) 

Shenandoah收集器的工作過程:

圖中黃色的區域代表的是被選入回收集的Region,綠色部分就代表還存活的對象,藍色就是用戶線程可以用來分配對象的內存Region了。圖3-16中不僅展示了Shenandoah三個併發階段的工作過程,還能形象地表示出併發標記階段如何找出回收對象確定回收集,併發回收階段如何移動回收集中的存活對象,併發引用更新階段如何將指向回收集中存活對象的所有引用全部修正,此後回收集便不存在任何引用可達的存活對象了。

 

詳細過程圖:

 

 

Brooks Pointer

“Brooks”是一個人的名字。1984年,RodneyA.Brooks在論文《Trading Data Space for Reduced Time and Code Space inReal-Time Garbage Collection on Stock Hardware》中提出了使用轉發指針(Forwarding Pointer,也常被稱爲Indirection Pointer)來實現對象移動與用戶程序併發的一種解決方案。此前,要做類似的併發操作,通常是在被移動對象原有的內存上設置保護陷阱(Memory Protection Trap),一旦用戶程序訪問到歸屬於舊對象的內存空間就會產生自陷中段,進入預設好的異常處理器中,再由其中的代碼邏輯把訪問轉發到複製後的新對象上。雖然確實能夠實現對象移動與用戶線程併發,但是如果沒有操作系統層面的直接支持,這種方案將導致用戶態頻繁切換到核心態,代價是非常大的,不能頻繁使用。

 

Brooks提出的新方案不需要用到內存保護陷阱,而是在原有對象佈局結構的最前面統一增加一個新的引用字段,在正常不處於併發移動的情況下,該引用指向對象自己。

從結構上來看,Brooks提出的轉發指針與某些早期Java虛擬機使用過的句柄定位(關於對象定位詳見第2章)有一些相似之處,兩者都是一種間接性的對象訪問方式,差別是句柄通常會統一存儲在專門的句柄池中,而轉發指針是分散存放在每一個對象頭前面。

有了轉發指針之後,有何收益暫且不論,所有間接對象訪問技術的缺點都是相同的,也是非常顯著的——每次對象訪問會帶來一次額外的轉向開銷,儘管這個開銷已經被優化到只有一行彙編指令的程度,譬如以下所示:

不過,畢竟對象定位會被頻繁使用到,這仍是一筆不可忽視的執行成本,只是它比起內存保護陷阱的方案已經好了很多。轉發指針加入後帶來的收益自然是當對象擁有了一份新的副本時,只需要修改一處指針的值,即舊對象上轉發指針的引用位置,使其指向新對象,便可將所有對該對象的訪問轉發到新的副本上。這樣只要舊對象的內存仍然存在,未被清理掉,虛擬機內存中所有通過舊引用地址訪問的代碼便仍然可用,都會被自動轉發到新對象上繼續工作

需要注意,Brooks形式的轉發指針在設計上決定了它是必然會出現多線程競爭問題的,如果收集器線程與用戶線程發生的只是併發讀取,那無論讀到舊對象還是新對象上的字段,返回的結果都應該是一樣的,這個場景還可以有一些“偷懶”的處理餘地;但如果發生的是併發寫入,就一定必須保證寫操作只能發生在新複製的對象上,而不是寫入舊對象的內存中。

不妨設想以下三件事情併發進行時的場景:

1)收集器線程複製了新的對象副本;

2)用戶線程更新對象的某個字段;

3)收集器線程更新轉發指針的引用值爲新副本地址。

解決方案:

如果不做任何保護措施,讓事件2在事件1、事件3之間發生的話,將導致的結果就是用戶線程對對象的變更發生在舊對象上,所以這裏必須針對轉發指針的訪問操作採取同步措施,讓收集器線程或者用戶線程對轉發指針的訪問只有其中之一能夠成功,另外一個必須等待,避免兩者交替進行。實際上Shenandoah收集器是通過比較並交換(Compare And Swap,CAS)操作來保證併發時對象的訪問正確性的。

轉發指針另一點必須注意的是執行頻率的問題,儘管通過對象頭上的Brooks Pointer來保證併發時原對象與複製對象的訪問一致性,這件事情只從原理上看是不復雜的,但是“對象訪問”這四個字的分量是非常重的,對於一門面向對象的編程語言來說,對象的讀取、寫入,對象的比較,爲對象哈希值計算,用對象加鎖等,這些操作都屬於對象訪問的範疇,它們在代碼中比比皆是,要覆蓋全部對象訪問操作,Shenandoah不得不同時設置讀、寫屏障去攔截。

之前介紹其他收集器時,或者是用於維護卡表,或者是用於實現併發標記,寫屏障已被使用多次,累積了不少的處理任務了,這些寫屏障有相當一部分在Shenandoah收集器中依然要被使用到。除此以外,爲了實現Brooks Pointer,Shenandoah在讀、寫屏障中都加入了額外的轉發處理,尤其是使用讀屏障的代價,這是比寫屏障更大的。代碼裏對象讀取的出現頻率要比對象寫入的頻率高出很多,讀屏障數量自然也要比寫屏障多得多,所以讀屏障的使用必須更加謹慎,不允許任何的重量級操作。

Shenandoah在實際應用中的性能表現

RedHat官方在2016年所發表的Shenandoah實現論文中給出的應用實測數據,測試內容是使用ElasticSearch對200GB的維基百科數據進行索引

從結果來看,應該說2016年做該測試時的Shenandoah並沒有完全達成預定目標,停頓時間比其他幾款收集器確實有了質的飛躍,但也並未實現最大停頓時間控制在十毫秒以內的目標,而吞吐量方面則出現了很明顯的下降,其總運行時間是所有測試收集器中最長的。讀者可以從這個官方的測試結果來對Shenandoah的弱項(高運行負擔使得吞吐量下降)和強項(低延遲時間)建立量化的概念,並對比一下稍ZGC的測試結果。

 

  • 優點:延遲低
  • 缺點:高運行負擔使得吞吐量下降;使用大量的讀寫屏障,尤其是讀屏障,增大了系統的性能開銷;

 

二、ZGC收集器

介紹

這款收集器的名字就叫作Z GarbageCollector)是一款在JDK 11中新加入的具有實驗性質[插圖]的低延遲垃圾收集器,是由Oracle公司研發的。2018年Oracle創建了JEP 333將ZGC提交給OpenJDK,推動其進入OpenJDK 11的發佈清單之中。

ZGC和Shenandoah對比:

ZGC和Shenandoah的目標是高度相似的,都希望在儘可能對吞吐量影響不太大的前提下,實現在任意堆內存大小下都可以把垃圾收集的停頓時間限制在十毫秒以內的低延遲。但是ZGC和Shenandoah的實現思路又是差異顯著的,如果說RedHat公司開發的Shen-andoah像是Oracle的G1收集器的實際繼承者的話,那Oracle公司開發的ZGC就更像是Azul System公司獨步天下的PGC(Pauseless GC)和C4(ConcurrentContinuously Compacting Collector)收集器的同胞兄弟。

首先從ZGC的內存佈局說起。與Shenandoah和G1一樣,ZGC也採用基於Region的堆內存佈局,但與它們不同的是,ZGC的Region(在一些官方資料中將它稱爲Page或者ZPage,本章爲行文一致繼續稱爲Region)具有動態性——動態創建和銷燬,以及動態的區域容量大小。

在x64硬件平臺下,ZGC的Region可以具大、中、小三類容量.

小型Region(Small Region):容量固定爲2MB,用於放置小於256KB的小對象。

中型Region(Medium Region):容量固定爲32MB,用於放置大於等於256KB但小於4MB的對象。

大型Region(Large Region):容量不固定,可以動態變化,但必須爲2MB的整數倍,用於放置4MB或以上的大對象。每個大型Region中只會存放一個大對象,這也預示着雖然名字叫作“大型Region”,但它的實際容量完全有可能小於中型Region,最小容量可低至4MB。大型Region在ZGC的實現中是不會被重分配(重分配是ZGC的一種處理動作,用於複製對象的收集器階段,稍後會介紹到)的,因爲複製一個大對象的代價非常高昂。

 

 

ZGC的功能實現:

接下來是ZGC的核心問題——併發整理算法的實現。Shenandoah使用轉發指針和讀屏障來實現併發整理,ZGC雖然同樣用到了讀屏障,但用的卻是一條與Shenandoah完全不同,更加複雜精巧的解題思路。

ZGC收集器有一個標誌性的設計是它採用的染色指針技術(Colored Pointer,其他類似的技術中可能將它稱爲Tag Pointer或者Version Pointer)。

染色指針是一種直接將少量額外的信息存儲在指針上的技術,可是爲什麼指針本身也可以存儲額外信息呢?在64位系統中,理論可以訪問的內存高達16EB(2的64次冪)字節[插圖]。實際上,基於需求(用不到那麼多內存)、性能(地址越寬在做地址轉換時需要的頁表級數越多)和成本(消耗更多晶體管)的考慮,在AMD64架構[插圖]中只支持到52位(4PB)的地址總線和48位(256TB)的虛擬地址空間,所以目前64位的硬件實際能夠支持的最大內存只有256TB。此外,操作系統一側也還會施加自己的約束,64位的Linux則分別支持47位(128TB)的進程虛擬地址空間和46位(64TB)的物理地址空間,64位的Windows系統甚至只支持44位(16TB)的物理地址空間。

儘管Linux下64位指針的高18位不能用來尋址,但剩餘的46位指針所能支持的64TB內存在今天仍然能夠充分滿足大型服務器的需要。鑑於此,ZGC的染色指針技術繼續盯上了這剩下的46位指針寬度,將其高4位提取出來存儲四個標誌信息。通過這些標誌位,虛擬機可以直接從指針中看到其引用對象的三色標記狀態、是否進入了重分配集(即被移動過)、是否只能通過finalize()方法才能被訪問到,如圖3-20所示。當然,由於這些標誌位進一步壓縮了原本就只有46位的地址空間,也直接導致ZGC能夠管理的內存不可以超過4TB(2的42次冪)

儘管Linux下64位指針的高18位不能用來尋址,但剩餘的46位指針所能支持的64TB內存在今天仍然能夠充分滿足大型服務器的需要。鑑於此,ZGC的染色指針技術繼續盯上了這剩下的46位指針寬度,將其高4位提取出來存儲四個標誌信息。通過這些標誌位,虛擬機可以直接從指針中看到其引用對象的三色標記狀態、是否進入了重分配集(即被移動過)、是否只能通過finalize()方法才能被訪問到,如圖3-20所示。當然,由於這些標誌位進一步壓縮了原本就只有46位的地址空間,也直接導致ZGC能夠管理的內存不可以超過4TB(2的42次冪)

 

染色指針的三大優勢:

1、 染色指針可以使得一旦某個Region的存活對象被移走之後,這個Region立即就能夠被釋放和重用掉,而不必等待整個堆中所有指向該Region的引用都被修正後才能清理。這點相比起Shenandoah是一個頗大的優勢,使得理論上只要還有一個空閒Region,ZGC就能完成收集,而Shenandoah需要等到引用更新階段結束以後才能釋放回收集中的Region,這意味着堆中幾乎所有對象都存活的極端情況,需要1∶1複製對象到新Region的話,就必須要有一半的空閒Region來完成收集。至於爲什麼染色指針能夠導致這樣的結果,筆者將在後續解釋其“自愈”特性的時候進行解釋。

2、 染色指針可以大幅減少在垃圾收集過程中內存屏障的使用數量,設置內存屏障,尤其是寫屏障的目的通常是爲了記錄對象引用的變動情況,如果將這些信息直接維護在指針中,顯然就可以省去一些專門的記錄操作。實際上,到目前爲止ZGC都並未使用任何寫屏障,只使用了讀屏障(一部分是染色指針的功勞,一部分是ZGC現在還不支持分代收集,天然就沒有跨代引用的問題)。內存屏障對程序運行時性能的損耗在前面章節中已經講解過,能夠省去一部分的內存屏障,顯然對程序運行效率是大有裨益的,所以ZGC對吞吐量的影響也相對較低。

3、 染色指針可以作爲一種可擴展的存儲結構用來記錄更多與對象標記、重定位過程相關的數據,以便日後進一步提高性能。現在Linux下的64位指針還有前18位並未使用,它們雖然不能用來尋址,卻可以通過其他手段用於信息記錄。如果開發了這18位,既可以騰出已用的4個標誌位,將ZGC可支持的最大堆內存從4TB拓展到64TB,也可以利用其餘位置再存儲更多的標誌,譬如存儲一些追蹤信息來讓垃圾收集器在移動對象時能將低頻次使用的對象移動到不常訪問的內存區域。

不過,要順利應用染色指針有一個必須解決的前置問題:Java虛擬機作爲一個普普通通的進程,這樣隨意重新定義內存中某些指針的其中幾位,操作系統是否支持?處理器是否支持?這是很現實的問題,無論中間過程如何,程序代碼最終都要轉換爲機器指令流交付給處理器去執行,處理器可不會管指令流中的指針哪部分存的是標誌位,哪部分纔是真正的尋址地址,只會把整個指針都視作一個內存地址來對待。這個問題在Solaris/SPARC平臺上比較容易解決,因爲SPARC硬件層面本身就支持虛擬地址掩碼,設置之後其機器指令直接就可以忽略掉染色指針中的標誌位。但在x86-64平臺上並沒有提供類似的黑科技,ZGC設計者就只能採取其他的補救措施了,這裏面的解決方案要涉及虛擬內存映射技術。

 

虛擬內存映射技術:

不同層次的虛擬內存到物理內存的轉換關係可以在硬件層面、操作系統層面或者軟件進程層面實現,如何完成地址轉換,是一對一、多對一還是一對多的映射,也可以根據實際需要來設計。Linux/x86-64平臺上的ZGC使用了多重映射(Multi-Mapping)將多個不同的虛擬內存地址映射到同一個物理內存地址上,這是一種多對一映射,意味着ZGC在虛擬內存中看到的地址空間要比實際的堆內存容量來得更大。把染色指針中的標誌位看作是地址的分段符,那隻要將這些不同的地址段都映射到同一個物理內存空間,經過多重映射轉換後,就可以使用染色指針正常進行尋址了

 

ZGC收集器是如何工作

 

ZGC的運作過程大致可劃分爲以下四個大的階段。全部四個階段都是可以併發執行的,僅是兩個階段中間會存在短暫的停頓小階段,這些小階段,譬如初始化GC Root直接關聯對象的Mark Start,與之前G1和Shenandoah的Initial Mark階段並沒有什麼差異

1、併發標記(Concurrent Mark):

與G1、Shenandoah一樣,併發標記是遍歷對象圖做可達性分析的階段,前後也要經過類似於G1、Shenandoah的初始標記、最終標記(儘管ZGC中的名字不叫這些)的短暫停頓,而且這些停頓階段所做的事情在目標上也是相類似的。與G1、Shenandoah不同的是,ZGC的標記是在指針上而不是在對象上進行的,標記階段會更新染色指針中的Marked 0、Marked 1標誌位。

2、併發預備重分配(Concurrent Prepare for Relocate):這個階段需要根據特定的查詢條件統計得出本次收集過程要清理哪些Region,將這些Region組成重分配集(Relocation Set)。重分配集與G1收集器的回收集(Collection Set)還是有區別的,ZGC劃分Region的目的並非爲了像G1那樣做收益優先的增量回收。相反,ZGC每次回收都會掃描所有的Region,用範圍更大的掃描成本換取省去G1中記憶集的維護成本。因此,ZGC的重分配集只是決定了裏面的存活對象會被重新複製到其他的Region中,裏面的Region會被釋放,而並不能說回收行爲就只是針對這個集合裏面的Region進行,因爲標記過程是針對全堆的。此外,在JDK 12的ZGC中開始支持的類卸載以及弱引用的處理,也是在這個階段中完成的。

3、併發重分配(Concurrent Relocate):重分配是ZGC執行過程中的核心階段,這個過程要把重分配集中的存活對象複製到新的Region上,併爲重分配集中的每個Region維護一個轉發表(Forward Table),記錄從舊對象到新對象的轉向關係。得益於染色指針的支持,ZGC收集器能僅從引用上就明確得知一個對象是否處於重分配集之中,如果用戶線程此時併發訪問了位於重分配集中的對象,這次訪問將會被預置的內存屏障所截獲,然後立即根據Region上的轉發表記錄將訪問轉發到新複製的對象上,並同時修正更新該引用的值,使其直接指向新對象,ZGC將這種行爲稱爲指針的“自愈”(Self-Healing)能力。這樣做的好處是隻有第一次訪問舊對象會陷入轉發,也就是隻慢一次,對比Shenandoah的Brooks轉發指針,那是每次對象訪問都必須付出的固定開銷,簡單地說就是每次都慢,因此ZGC對用戶程序的運行時負載要比Shenandoah來得更低一些。還有另外一個直接的好處是由於染色指針的存在,一旦重分配集中某個Region的存活對象都複製完畢後,這個Region就可以立即釋放用於新對象的分配(但是轉發表還得留着不能釋放掉),哪怕堆中還有很多指向這個對象的未更新指針也沒有關係,這些舊指針一旦被使用,它們都是可以自愈的。

4、併發重映射(Concurrent Remap):重映射所做的就是修正整個堆中指向重分配集中舊對象的所有引用,這一點從目標角度看是與Shenandoah併發引用更新階段一樣的,但是ZGC的併發重映射並不是一個必須要“迫切”去完成的任務,因爲前面說過,即使是舊引用,它也是可以自愈的,最多隻是第一次使用時多一次轉發和修正操作。重映射清理這些舊引用的主要目的是爲了不變慢(還有清理結束後可以釋放轉發表這樣的附帶收益),所以說這並不是很“迫切”。因此,ZGC很巧妙地把併發重映射階段要做的工作,合併到了下一次垃圾收集循環中的併發標記階段裏去完成,反正它們都是要遍歷所有對象的,這樣合併就節省了一次遍歷對象圖的開銷。一旦所有指針都被修正之後,原來記錄新舊對象關係的轉發表就可以釋放掉了。

處理過程:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Shenandoah GC - Part I: The Garbage Collector That Could

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章