ZGC
Z Garbage Collector,ZGC 是從 JDK11 中引入的一種新的支持彈性伸縮和低延遲垃圾收集器
G1 設計上的特點,導致存在以下問題:
停頓時間過長
通常 G1 的停頓時間要達到幾十到幾百毫秒;這個數字其實已經非常小了,但是我們知道垃圾回收發生導致應用程序在這幾十或者幾百毫秒中不能提供服務,在某些場景中,特別是對用戶體驗有較高要求的情況下不能滿足實際需求
內存利用率不高
通常引用關系的處理需要額外消耗內存,一般占整個內存的 1%~20% 左右
支持的內存空間有限
不適用於超大內存的系統,特別是在內存容量高於 100GB 的系統中,會因內存過大而導致停頓時間增長
ZGC 四大目標
- 支持
TB級內存 - 停頓時間控制在
10ms之內 - 奠定未來 GC 特性的基礎
- 對程序吞吐量影響小於
15%

Oracle 官方提到了它最大的優點是:它的停頓時間不會隨着堆的增大而增長!也就是說,幾十
G堆的停頓時間是10ms以下,幾百G甚至上T堆的停頓時間也是10ms以下
ZGC 特點
- ZGC 最典型的特性是它是一款並發(concurrent)的 GC
- 它可以標記內存,復制和遷移(relocate)內存,所有的操作都是並發的,同時它有一個並發的引用處理器
- 其它的垃圾收集器都是使用
store barriers,ZGC 使用load barriers - ZGC 可以更加靈活的配置大小和策略,相比於 G1,它可以更好的處理非常大(very large)對象的釋放
- ZGC 只有一代,沒有新生代,老年代什么的
- ZGC 依賴
NUMA-aware(非均衡存儲器訪問) - parse 時間在
10ms以內 → JDK16 以后,ZGC 的停頓時間為O(1),不會超過1ms - 在
JDK14之前,ZGC 只能用於 Linux 上,現在也可以使用在windows上了 - 不支持壓縮類指針與壓縮指針 →
JDK15以后開始支持壓縮類指針
ZGC 內存布局
基於 Region 內存布局的,暫時不設分代的,使用了讀屏障、顏色指針等技術來實現可並發的標記-整理算法的,以低延遲為首要目標的一款垃圾收集器
ZGC 的 Region 分為大、中、小三類容量:
小型 Region(Small Region)
容量固定為 2MB,用於放置小於 256KB 的小對象
中型 Region(Medium Region)
容量固定為 32MB,用於放置大於等於 256KB 但小於 4MB 的對象
大型 Region(Large Region)
容量不固定,可以動態變化,但必須為 2MB 的整數倍,用於放置 4MB 或以上的大對象

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

NUMA
Non Uniform Memory Access Architecture,NUMA 的話每個 CPU 對應有一塊內存,且這塊內存在主板上離這個 CPU 是最近的,每個 CPU 優先訪問這塊內存,那效率自然就提高了
顏色指針
HotSpot 的垃圾收集器,有幾種不同的標記實現方案:
- 把標記直接記錄在對象頭上(Serial 收集器)
- 把標記記錄在於對象相互獨立的數據結構上,G1 使用了一種
BitMap的結構來記錄標記信息 - bitmap 堆的
1/64
Colored Pointers,即顏色指針,染色指針是一種直接將少量額外的信息存儲在指針上的技術,ZGC 的 GC 信息保存在指針中。每個對象有一個 64 位指針,18 位,預留給以后使用
地址視圖的狀態
1位
Finalizable 標識,此位與並發引用處理有關,它表示這個對象只能通過 finalizer 才能訪問,當前的對象正在回收
1位
Remapped 標識,在垃圾回收開始前視圖是 Remapped,正在標記
1位
Marked1 標識,每一個 GC 周期開始時,會交換使用的標記位,使上次 GC 周期中修正的已標記狀態失效,所有引用都變成未標記,GC 周期1:使用 mark0,則周期結束所有引用 mark 標記都會成為 01。GC 周期2:使用 mark1,則期待的 mark 標記 10,所有引用都能被重新標記
1位
Marked0 標識,和上面的 Marked1 都是標記對象用於輔助 GC,在標記階段結束之后,對象的地址視圖要么是 M0(活躍),要么是 Remapped(垃圾)
42位
對象的地址(所以它可以支持 2^42=4T 內存)JDK13 16TB 低 44 位
ZGC 將其高 4 位提取出來存儲四個標志信息,通過這些標志虛擬機就可以直接從指針中看到引用對象的三色標記狀態,是否進入了重分配集(Marked0、Marked1),是否被移動過(Remapped),是否只能通過 finalize() 方法才能被訪問到(Finalizable)

優勢
顏色指針可以使得一旦某個 Region 的存活對象被移走之后,這個 Region 立即就能夠被釋放和重用掉,而不必等待整個堆中所有指令向該 Region 的引用都被修正后才能清理,染色指針可以大幅減少在垃圾收集過程中內存屏障的使用數量,設置內存屏障,尤其是在寫屏障的目的通常是為了記錄對象引用的變動情況。如果將這些信息直接維護在指針中,顯然就可以省去一些專門的記錄操作,染色指針可以作為一種可擴展的存儲結構用來記錄更多與對象標記、重定位過程相關的數據,以便日后進一步提高性能
回收過程
並發標記 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)能力
並發重映射 Concurrent Remap
重映射所做的就是修正整個堆中指向重分配集中舊對象的所有引用,但是 ZGC 中對象引用存在 “自愈” 功能,所以這個重映射操作並不是很迫切,ZGC 的並發映射並不是以一個必須要 “迫切” 去完成的任務。ZGC 很巧妙地把並發重映射階段要做的工作,合並到下一次垃圾收集循環中的並發標記階段里去完成,他們都是要遍歷所有對象的,這樣合並節省了一次遍歷的開銷
讀屏障
讀一個對象的時候,會加上 load barrier,檢測是不是 bad color,mark / relocate / remap,等完成拿到新地址,再返回



ZGC 的優劣勢
缺點
浮動垃圾
當 ZGC 准備要對一個很大的堆做一次完整的並發收集,持續十分鍾以上,由於應用的對象分配速率很高,將創造大量的新對象,這些新對象很難進入當次收集的標記范圍,通常就只能全部作為存活對象來看,目前唯一的辦法就是盡可能地去增加堆容量大小,獲取更多喘息的時間。但若要從根本上解決,還是需要引入分代收集,讓新生對象都在一個專門的區域中創建,然后針對這個區域進行更頻繁、更快的收集
優點
高吞吐量、低延遲
ZGC 是支持 “NUMA-Aware” 的內存分配。NUMA(Non-Uniform Memory Access,非統一內存訪問架構)是一種多處理器或多核處理器計算機所設計的內存架構。ZGC 默認支持 NUMA 架構,在創建對象時,根據當前線程在哪個 CPU 執行,優先在靠近這個 CPU 的內存進行分配,這樣可以顯著的提高性能,在 SPEC JBB 2005 基准測試里獲得 40% 的提升
