JVM - ZGC初探

在這裏插入圖片描述

Pre

JVM - G1初探


ZGC概述

ZGC是一款JDK 11中新加入的具有實驗性質的低延遲垃圾收集器,ZGC源自於是Azul System公司開發的C4(Concurrent Continuously Compacting Collector) 收集器。

在這裏插入圖片描述

目前很少有公司使用,可適當瞭解,擴展知識面。


ZGC的目標

在這裏插入圖片描述

  • 支持TB量級的堆。 這可是上T的內存哇,誰家這麼壕~
  • 最大GC停頓時間不超10ms。目前一般線上環境運行良好的JAVA應用Minor GC停頓時間在10ms左右,Major GC一般都需要100ms以上(G1可以調節停頓時間,但是如果調的過低的話,反而會適得其反),之所以能做到這一點是因爲它的停頓時間主要跟Root掃描有關,而Root數量和堆大小是沒有任何關係的。
  • 奠定未來GC特性的基礎。
  • 最糟糕的情況下吞吐量會降低15%。這都不是事,停頓時間足夠優秀。至於吞吐量,通過擴容分分鐘解決。

另外,Oracle官方提到了它最大的優點是:它的停頓時間不會隨着堆的增大而增長!也就是說,幾十G堆的停頓時間是10ms以下,幾百G甚至上T堆的停頓時間也是10ms以下。


參數

在這裏插入圖片描述


不分代(暫時)

單代,即ZGC「沒有分代」。

以前的垃圾回收器之所以分代,是因爲源於“「大部分對象朝生夕死」”的假設,事實上大部分系統的對象分配行爲也確實符合這個假設。


ZGC的內存佈局

ZGC收集器是一款基於Region內存佈局的, 暫時不設分代的, 使用了讀屏障、 顏色指針等技術來實現可併發的標記-整理算法的, 以低延遲爲首要目標的一款垃圾收集器。

ZGC的Region可以具有大、 中、 小三類容量:

  • 小型Region(Small Region) : 容量固定爲2MB, 用於放置小於256KB的小對象。
  • 中型Region(Medium Region) : 容量固定爲32MB, 用於放置大於等於256KB但小於4MB的對象。
  • 大型Region(Large Region) : 容量不固定, 可以動態變化, 但必須爲2MB的整數倍, 用於放置4MB或以上的大對象。

在這裏插入圖片描述


NUMA-aware 非統一內存訪問自動感知

UMA即Uniform Memory Access Architecture

NUMA是Non Uniform Memory Access Architecture

UMA表示內存只有一塊,所有CPU都去訪問這一塊內存,那麼就會存在競爭問題(爭奪內存總線訪問權),有競爭就會有鎖,有鎖效率就會受到影響,而且CPU核心數越多,競爭就越激烈。

NUMA的話每個CPU對應有一塊內存,且這塊內存在主板上離這個CPU是最近的,每個CPU優先訪問這塊內存,那效率自然就提高了

在這裏插入圖片描述


ZGC的回收階段

在這裏插入圖片描述

  • 併發標記(Concurrent Mark):

與G1一樣,併發標記是遍歷對象圖做可達性分析的階段,它的初始標記(Mark Start)和最終標記(Mark End)也會出現短暫的停頓,與G1不同的是, ZGC的標記是在指針上而不是在對象上進行的, 標記階段會更新顏色指針(見下圖)Marked 0、 Marked 1標誌位

  • 併發預備重分配(Concurrent Prepare for Relocate)

根據特定的查詢條件統計得出本次收集過程要清理哪些Region,將這些Region組成重分配集(Relocation Set)。ZGC每次回收都會掃描所有的Region,用範圍更大的掃描成本換取省去G1中記憶集的維護成本。

  • 併發重分配(Concurrent Relocate)

核心階段

把重分配集中的存活對象複製到新的Region上,併爲重分配集中的每個Region維護一個轉發表(Forward Table),記錄從舊對象到新對象的轉向關係。ZGC收集器能僅從引用上就明確得知一個對象是否處於重分配集之中,如果用戶線程此時併發訪問了位於重分配集中的對象,這次訪問將會被預置的內存屏障(讀屏障(見下面詳解))所截獲,然後立即根據Region上的轉發表記錄將訪問轉發到新複製的對象上,並同時修正更新該引用的值,使其直接指向新對象,ZGC將這種行爲稱爲指針的“自愈”(Self-Healing)能力。

ZGC的顏色指針因爲“自愈”(Self-Healing)能力,所以只有第一次訪問舊對象會變慢, 一旦重分配集中某個Region的存活對象都複製完畢後, 這個Region就可以立即釋放用於新對象的分配,但是轉發表還得留着不能釋放掉, 因爲可能還有訪問在使用這個轉發表。

  • 併發重映射(Concurrent Remap)

修正整個堆中指向重分配集中舊對象的所有引用,但是ZGC中對象引用存在“自愈”功能,所以這個重映射操作並不是很迫切。ZGC很巧妙地把併發重映射階段要做的工作,合併到了下一次垃圾收集循環中的併發標記階段裏去完成,反正它們都是要遍歷所有對象的,這樣合併就節省了一次遍歷對象圖的開銷。一旦所有指針都被修正之後, 原來記錄新舊對象關係的轉發表就可以釋放掉了。


顏色指針

在這裏插入圖片描述

ZGC的核心設計之一。以前的垃圾回收器的GC信息都保存在對象頭中,而ZGC的GC信息保存在指針中。

每個對象有一個64位指針,這64位被分爲:

  • 18位:預留給以後使用;
  • 1位:Finalizable標識,此位與併發引用處理有關,它表示這個對象只能通過finalizer才能訪問;
  • 1位:Remapped標識,設置此位的值後,對象未指向relocation set中(relocation set表示需要GC的Region集合);
  • 1位:Marked1標識;
  • 1位:Marked0標識,和上面的Marked1都是標記對象用於輔助GC;
  • 42位:對象的地址(所以它可以支持2^42=4T內存)

爲什麼有2個mark標記?

每一個GC週期開始時,會交換使用的標記位,使上次GC週期中修正的已標記狀態失效,所有引用都變成未標記。
GC週期1:使用mark0, 則週期結束所有引用mark標記都會成爲01。
GC週期2:使用mark1, 則期待的mark標記10,所有引用都能被重新標記。

通過對配置ZGC後對象指針分析我們可知,對象指針必須是64位,那麼ZGC就無法支持32位操作系統,同樣的也就無法支持壓縮指針了(CompressedOops,壓縮指針也是32位)。


顏色指針的三大優勢

  1. 一旦某個Region的存活對象被移走之後,這個Region立即就能夠被釋放和重用掉,而不必等待整個堆中所有指向該Region的引用都被修正後才能清理,這使得理論上只要還有一個空閒Region,ZGC就能完成收集。
  2. 顏色指針可以大幅減少在垃圾收集過程中內存屏障的使用數量,ZGC只使用了讀屏障
  3. 顏色指針具備強大的擴展性,它可以作爲一種可擴展的存儲結構用來記錄更多與對象標記、重定位過程相關的數據,以便日後進一步提高性能。

讀屏障

在這裏插入圖片描述
之前的GC都是採用Write Barrier,這次ZGC採用了完全不同的方案讀屏障,這個是ZGC一個非常重要的特性。

在標記和移動對象的階段,每次「從堆裏對象的引用類型中讀取一個指針」的時候,都需要加上一個Load Barriers。


ZGC觸發時機

ZGC目前有4中機制觸發GC:

  • 定時觸發,默認爲不使用,可通過ZCollectionInterval參數配置。
  • 預熱觸發,最多三次,在堆內存達到10%、20%、30%時觸發,主要時統計GC時間,爲其他GC機制使用。
  • 分配速率,基於正態分佈統計,計算內存99.9%可能的最大分配速率,以及此速率下內存將要耗盡的時間點,在耗盡之前觸發GC(耗盡時間 - 一次GC最大持續時間 - 一次GC檢測週期時間)。
  • 主動觸發,(默認開啓,可通過ZProactive參數配置) 距上次GC堆內存增長10%,或超過5分鐘時,對比距上次GC的間隔時間跟(49 * 一次GC的最大持續時間),超過則觸發。

存在的問題[浮動垃圾]

ZGC最大的問題是浮動垃圾。ZGC的停頓時間是在10ms以下,但是ZGC的執行時間還是遠遠大於這個時間的。

假如ZGC全過程需要執行10分鐘,在這個期間由於對象分配速率很高,將創建大量的新對象,這些對象很難進入當次GC,所以只能在下次GC的時候進行回收,這些只能等到下次GC才能回收的對象就是浮動垃圾。

ZGC沒有分代概念,每次都需要進行全堆掃描,導致一些“朝生夕死”的對象沒能及時的被回收。

解決方案:

目前唯一的辦法是增大堆的容量,使得程序得到更多的喘息時間,但是這個也是一個治標不治本的方案。如果需要從根本上解決這個問題,還是需要引入分代收集,讓新生對象都在一個專門的區域中創建,然後專門針對這個區域進行更頻繁、更快的收集。


參考資料

https://wiki.openjdk.java.net/display/zgc/Main

http://cr.openjdk.java.net/~pliden/slides/ZGC-Jfokus-2018.pdf

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