深入理解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的運作過程大致可劃分為以下四個大的階段。四個階段都是可以併發執行的,僅是兩個階段中間會存在短暫的停頓小階段。
運作過程如下:

  • 併發標記(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%的提升。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!