天天看點

深入了解JVM - ZGC垃圾收集器

如果下面的一些概念有些不清楚的可以先看​​深入了解JVM - 垃圾收集器​​和​​深入了解JVM - Shenandoah垃圾收集器​​。

ZGC(Z Garbage Collector)是一款由Oracle公司研發的,以低延遲為首要目标的一款垃圾收集器。它是基于動态Region記憶體布局,(暫時)不設年齡分代,使用了讀屏障、染色指針和記憶體多重映射等技術來實作可并發的标記-整理算法的收集器。在JDK 11新加入,還在實驗階段,主要特點是:回收TB級記憶體(最大4T),停頓時間不超過10ms。

動态Region

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

深入了解JVM - ZGC垃圾收集器
  • 小型Region(Small Region):容量固定為2MB,用于放置小于256KB的小對象。
  • 中型Region(Medium Region):容量固定為32MB,用于放置大于等于256KB但小于4MB的對象。·
  • 大型Region(Large Region):容量不固定,可以動态變化,但必須為2MB的整數倍,用于放置4MB或以上的大對象。每個大型Region中隻會存放一個大對象,最小容量可低至4MB,所有大型Region可能小于中型Region。大型Region在ZGC的實作中是不會被重配置設定的,因為複制一個大對象的代價非常高昂。

染色指針技術

HotSpot虛拟機的标記實作方案有如下幾種:

  1. 把标記直接記錄在對象頭上(如Serial收集器);
  2. 把标記記錄在與對象互相獨立的資料結構上(如G1、Shenandoah使用了一種相當于堆記憶體的1/64大小的,稱為BitMap的結構來記錄标記資訊);
  3. 直接把标記資訊記在引用對象的指針上(如ZGC)

染色指針是一種直接将少量額外的資訊存儲在指針上的技術。目前在Linux下64位的作業系統中高18位是不能用來尋址的,但是剩餘的46為卻可以支援64T的空間,到目前為止我們幾乎還用不到這麼多記憶體。于是ZGC将46位中的高4位取出,用來存儲4個标志位,剩餘的42位可以支援4T的記憶體,如圖所示:

深入了解JVM - ZGC垃圾收集器
  • Linux下64位指針的高18位不能用來尋址,所有不能使用;
  • Finalizable:表示是否隻能通過finalize()方法才能被通路到,其他途徑不行;
  • Remapped:表示是否進入了重配置設定集(即被移動過);
  • Marked1、Marked0:表示對象的三色标記狀态;
  • 最後42用來存對象位址,最大支援4T;

三色标記

在并發的可達性分析算法中我們使用三色标記(Tri-color Marking)來标記對象是否被收集器通路過:

  • 白色:表示對象尚未被垃圾收集器通路過。顯然在可達性分析剛剛開始的階段,所有的對象都是白色的,若在分析結束的階段,仍然是白色的對象,即代表不可達。
  • 黑色:表示對象已經被垃圾收集器通路過,且這個對象的所有引用都已經掃描過。黑色的對象代表已經掃描過,它是安全存活的,如果有其他對象引用指向了黑色對象,無須重新掃描一遍。黑色對象不可能直接(不經過灰色對象)指向某個白色對象。
  • 灰色:表示對象已經被垃圾收集器通路過,但這個對象上至少存在一個引用還沒有被掃描過。

可達性分析的掃描過程,其實就是一股以灰色為波峰的波紋從黑向白推進的過程,但是在并發的推進過程中會産生“對象消失”的問題,如圖:、

深入了解JVM - ZGC垃圾收集器

對象消失理論,隻有同時滿足才會發生對象消失:

  • 指派器插入了一條或多條從黑色對象到白色對象的新引用;
  • 指派器删除了全部從灰色對象到該白色對象的直接或間接引用;

要解決對象消失問題隻需要破壞其中一條就行了,目前常用有兩種方案:

  • 增量更新(Incremental Update):增量更新要破壞的是第一個條件,當黑色對象插入新的指向白色對象的引用關系時,就将這個新插入的引用記錄下來,等并發掃描結束之後,再将這些記錄過的引用關系中的黑色對象為根,重新掃描一次。這可以簡化了解為,黑色對象一旦新插入了指向白色對象的引用之後,它就變回灰色對象了。
  • 原始快照(Snapshot At TheBeginning,SATB):原始快照要破壞的是第二個條件,當灰色對象要删除指向白色對象的引用關系時,就将這個要删除的引用記錄下來,在并發掃描結束之後,再将這些記錄過的引用關系中的灰色對象為根,重新掃描一次。這也可以簡化了解為,無論引用關系删除與否,都會按照剛剛開始掃描那一刻的對象圖快照來進行搜尋。
以上無論是對引用關系記錄的插入還是删除,虛拟機的記錄操作都是通過寫屏障實作的。CMS是基于增量更新來做并發标記的,G1、Shenandoah則是用原始快照來實作。

染色指針的三大優勢

  1. 一旦某個Region的存活對象被移走之後,這個Region立即就能夠被釋放和重用掉,而不必等待整個堆中所有指向該Region的引用都被修正後才能清理,這使得理論上隻要還有一個空閑Region,ZGC就能完成收集。而Shenandoah需要等到更新階段結束才能釋放回收集中的Region,如果Region裡面對象都存活的時候,需要1:1的空間才能完成收集。
  2. 染色指針可以大幅減少在垃圾收集過程中記憶體屏障的使用數量,ZGC隻使用了讀屏障。
  3. 染色指針具備強大的擴充性,它可以作為一種可擴充的存儲結構用來記錄更多與對象标記、重定位過程相關的資料,以便日後進一步提高性能。

記憶體多重映射

ZGC使用了記憶體多重映射(Multi-Mapping)将多個不同的虛拟記憶體位址映射到同一個實體記憶體位址上,這是一種多對一映射,意味着ZGC在虛拟記憶體中看到的位址空間要比實際的堆記憶體容量來得更大。把染色指針中的标志位看作是位址的分段符,那隻要将這些不同的位址段都映射到同一個實體記憶體空間,經過多重映射轉換後,就可以使用染色指針正常進行尋址了,效果如圖:

深入了解JVM - ZGC垃圾收集器
ZGC的多重映射隻是它采用染色指針技術的伴生産物

讀屏障

當對象從堆中加載的時候,就會使用到讀屏障(Load Barrier)。這裡使用讀屏障的主要作用就是檢查指針上的三色标記位,根據标記位判斷出對象是否被移動過,如果沒有可以直接通路,如果移動過就需要進行“自愈”(對象通路會變慢,但也隻會有一次變慢),當“自愈”完成後,後續通路就不會變慢了。

讀寫屏障可以了解成對象通路的“AOP”操作

ZGC運作過程

ZGC的運作過程大緻可劃分為以下四個大的階段:

深入了解JVM - ZGC垃圾收集器
  • 并發标記(Concurrent Mark):與G1、Shenandoah一樣,并發标記是周遊對象圖做可達性分析的階段,它的初始标記和最終标記也會出現短暫的停頓,整個标記階段隻會更新染色指針中的Marked 0、Marked 1标志位。
  • 并發預備重配置設定(Concurrent Prepare for Relocate):這個階段需要根據特定的查詢條件統計得出本次收集過程要清理哪些Region,将這些Region組成重配置設定集(Relocation Set)。ZGC每次回收都會掃描所有的Region,用範圍更大的掃描成本換取省去G1中記憶集的維護成本。
  • 并發重配置設定(Concurrent Relocate):重配置設定是ZGC執行過程中的核心階段,這個過程要把重配置設定集中的存活對象複制到新的Region上,并為重配置設定集中的每個Region維護一個轉發表(Forward Table),記錄從舊對象到新對象的轉向關系。ZGC收集器能僅從引用上就明确得知一個對象是否處于重配置設定集之中,如果使用者線程此時并發通路了位于重配置設定集中的對象,這次通路将會被預置的記憶體屏障所截獲,然後立即根據Region上的轉發表記錄将通路轉發到新複制的對象上,并同時修正更新該引用的值,使其直接指向新對象,ZGC将這種行為稱為指針的“自愈”(Self-Healing)能力。

ZGC的染色指針因為“自愈”(Self-Healing)能力,是以隻有第一次通路舊對象會變慢,而Shenandoah的Brooks轉發指針是每次都會變慢。

一旦重配置設定集中某個Region的存活對象都複制完畢後,這個Region就可以立即釋放用于新對象的配置設定,但是轉發表還得留着不能釋放掉,因為可能還有通路在使用這個轉發表。

  • 并發重映射(Concurrent Remap):重映射所做的就是修正整個堆中指向重配置設定集中舊對象的所有引用,但是ZGC中對象引用存在“自愈”功能,是以這個重映射操作并不是很迫切。ZGC很巧妙地把并發重映射階段要做的工作,合并到了下一次垃圾收集循環中的并發标記階段裡去完成,反正它們都是要周遊所有對象的,這樣合并就節省了一次周遊對象圖的開銷。

ZGC存在的問題

ZGC最大的問題是浮動垃圾。

浮動垃圾

ZGC的停頓時間是在10ms以下,但是ZGC的執行時間還是遠遠大于這個時間的。假如ZGC全過程需要執行10分鐘,在這個期間由于對象配置設定速率很高,将建立大量的新對象,這些對象很難進入當次GC,是以隻能在下次GC的時候進行回收,這些隻能等到下次GC才能回收的對象就是浮動垃圾。

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

解決方案

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

官方測試資料

停頓時間

在ZGC的停頓時間測試上,和其他收集器相比完全不在一個數量級,如圖:

深入了解JVM - ZGC垃圾收集器

吞吐量

ZGC的“弱項”吞吐量方面,以低延遲為首要目标的ZGC已經達到了以高吞吐量為目标Parallel Scavenge的99%,直接超越了G1,如圖:

優缺點

  • 優點:低停頓,高吞吐量,ZGC收集過程中額外耗費的記憶體小
  • 缺點:浮動垃圾

參考