深入理解JVM(③)ZGC收集器

前言

ZGC是一款在JDK11中新加入的具有實驗性質的低延遲垃圾收集器,目前僅支援Linux/x86-64。ZGC收集器是一款基於Region記憶體布局的,(暫時)不設分代的,使用了讀屏障、染色指針和記憶體多重映射等技術來實現可並發的標記-整理演算法的,以低延遲為首要目標的一款垃圾收集器。

ZGC布局

與Shenandoah和G1一樣,ZGC也採取基於Region的堆記憶體布局,但與他們不同的是,ZGC的Region具有動態性(動態的創建和銷毀,以及動態的區域容量大小)。
ZGC的Region可以分為三類:

  • 小型Region容量固定為2MB,用於放置小於256KB的小對象。
  • 中型Region容量固定為32MB,用於放置大於等於256KB但小於4MB的對象。
  • 大型Region容量不固定,可以動態變化,但必須為2MB的整數倍,用於存放4MB或以上的大對象。並且每個大型Region只會存放一個對象。
    ZGC記憶體布局圖:
    在這裡插入圖片描述

染色指針

HotSpot的垃圾收集器,有幾種不同的標記實現方案。

  • 把標記直接記錄在對象頭上(Serial 收集器)
  • 把標記記錄在於對象相互獨立的數據結構上(G1、Shenandoah使用了一種相當於堆記憶體的1/64大小的,稱為BitMap的結構來記錄標記資訊)
  • ZGC染色指針直接把標記資訊記載引用對象的指針上
    染色指針是一種直接將少量額外的資訊存儲在指針上的技術。
    目前Linux下64位指針的高18位不能用來定址,但剩餘的46位指針所能支援的64TB記憶體仍然鞥呢夠充分滿足大型伺服器的需要。鑒於此,ZGC將其高4位提取出來存儲四個標誌資訊。
    通過這些標誌虛擬機就可以直接從指針中看到器引用對象的三色標記狀態(Marked0、Marked1)、是否進入了重分配集(是否被移動過——Remapped)、是否只能通過finalize()方法才能被訪問到(Finalizable)。由於這些標誌位進一步壓縮了原本只有46位的地址空寂,導致ZGC能夠管理的記憶體不可以超過4TB。
    染色指針示意圖:
    染色指針示意圖

染色指針的優勢

  • 染色指針可以使得一旦某個Region的存活對象被移走之後,這個Region立即就能夠被釋放和重用掉,而不必等待整個堆中所有指令向該Region的引用都被修正後才能清理。
  • 染色指針可以大幅減少在垃圾收集過程中記憶體屏障的使用數量,設置記憶體屏障,尤其是在寫屏障的目的通常是為了記錄對象引用的變動情況,如果將這些資訊直接維護在指針中,顯然就可以省去一些專門的記錄操作。
  • 染色指針可以作為一種可擴展的存儲結構用來記錄更多與對象標記、重定位過程相關的數據,以便日後進一步提高性能。

記憶體多重映射

Linux/x86-64平台上ZGC使用了多重映射(Multi-Mapping)將多個不同的虛擬記憶體地址映射到同一物理記憶體地址上,這是一種多對一映射,意味著ZGC在虛擬記憶體中看到的地址空寂要比實際的堆記憶體容量來的更大。把染色指針中的標誌位看作是地址的分段符,那隻要將這些不同的地址段都映射到同一物理內褲空間,經過多重映射轉換後,就可以使用染色指針正常進行定址了。
多重映射下的定址:
在這裡插入圖片描述

ZGC的運作過程

ZGC的運作過程大致可劃分為以下四個大的階段。四個階段都是可以並發執行的,僅是兩個階段中間會存在短暫的停頓小階段。
運作過程如下:
ZGC運行過程

  • 並發標記(Concurrent Mark): 與G1、Shenandoah一樣,並發標記是遍歷對象圖做可達性分析的階段,前後也要經過類似於G1、Shenandoah的初始標記、最終標記的短暫停頓,而且這些停頓階段所做的事情在目標上也是相類似的。
  • 並發預備重分配( Concurrent Prepare for Relocate): 這個階段需要根據特定的查詢條件統計得出本次收集過程要清理哪些Region,將這些Region組成重分配集(Relocation Set)。
  • 並發重分配(Concurrent Relocate): 重分配是ZGC執行過程中的核心階段,這個過程要把重分配集中的存活對象複製到新的Region上,並為重分配集中的每個Region維護一個轉發表(Forward Table),記錄從舊對象到新對象的轉向關係。
  • 並發重映射(Concurrent Remap): 重映射所做的就是修正整個堆中指向重分配集中舊對象的所有引用,ZGC的並發映射並不是以一個必須要「迫切」去完成的任務。ZGC很巧妙地把並發重映射階段要做的工作,合併到下一次垃圾收集循環中的並發標記階段里去完成,反正他們都是要遍歷所有對象的,這樣合併節省了一次遍歷的開銷。

ZGC的優劣勢

  • 缺點:浮動垃圾
    當ZGC準備要對一個很大的堆做一次完整的並發收集,駕駛其全過程要持續十分鐘以上,由於應用的對象分配速率很高,將創造大量的新對象,這些新對象很難進入當次收集的標記範圍,通常就只能全部作為存活對象來看待(儘管其中絕大部分對象都是朝生夕滅),這就產生了大量的浮動垃圾。
    目前唯一的辦法就是儘可能地去增加堆容量大小,獲取更多喘息的時間。但若要從根本上解決,還是需要引入分代收集,讓新生對象都在一個專門的區域中創建,然後針對這個區域進行更頻繁、更快的收集。
  • 優點:高吞吐量、低延遲
    ZGC是支援「NUMA-Aware」的記憶體分配。MUMA(Non-Uniform Memory Access,非統一記憶體訪問架構)是一種多處理器或多核處理器電腦所設計的記憶體架構。
    現在多CPU插槽的伺服器都是Numa架構,比如兩顆CPU插槽(24核),64G記憶體的伺服器,那其中一顆CPU上的12個核,訪問從屬於它的32G本地記憶體,要比訪問另外32G遠端記憶體要快得多。
    ZGC默認支援NUMA架構,在創建對象時,根據當前執行緒在哪個CPU執行,優先在靠近這個CPU的記憶體進行分配,這樣可以顯著的提高性能,在SPEC JBB 2005 基準測試里獲得40%的提升。
Tags: