ZGC
ZGC和G1,CMS一樣都是一種垃圾回收器。那其實G1已經很不錯了
為什么還需要ZGC呢
ZGC(The Z Garbage Collector)是JDK 11中推出的一款低延遲垃圾回收器,它的設計目標包括:
-
停頓時間不超過10ms;
-
停頓時間不會隨著堆的大小,或者活躍對象的大小而增加;
-
支持8MB~4TB級別的堆(未來支持16TB)
先看下G1垃圾回收器
這里全紅的即為需要STW的
ZGC的流程
GC 的完整工作流程如下:
-
初始標記(Initial Mark)
-
目標:標記 GC Roots 直接關聯的對象。
-
操作:暫停所有應用線程(STW),快速標記根對象。
-
耗時:通常 <1ms。
-
-
并發標記(Concurrent Mark)
-
目標:遍歷整個對象圖,標記所有存活對象。
-
操作:GC 線程與應用線程并發執行,通過染色指針記錄對象狀態。
-
關鍵機制:
-
對象重定位(Relocation):標記過程中發現需要移動的對象,提前記錄遷移信息。
-
再標記(Remark):處理并發標記期間發生變化的對象引用。
-
-
-
并發轉移(Concurrent Transfer)
-
目標:將存活對象移動到新的內存區域,釋放舊內存。
-
操作:
-
預轉移(Pre-Transfer):為對象遷移做準備,更新元數據。
-
轉移(Transfer):GC 線程與應用線程并發遷移對象,確保業務線程無感知。
-
-
關鍵技術:
-
讀屏障(Load Barriers):在對象訪問時自動修正指針,指向新內存地址。
-
自愈(Self-Healing):若遷移過程中出現指針失效,可通過染色指針快速恢復。
-
-
-
最終清理(Final Cleanup)
-
目標:回收完全空閑的內存頁,重置 GC 周期。
-
操作:短暫 STW,清理元數據和統計信息。
-
ZGC關鍵技術
通過 并發標記 + 并發轉移 + 實時地址修正,實現零停頓。
ZGC 通過 著色指針(Colored Pointer) 和 讀屏障(Load Barriers) 技術,在對象轉移過程中實現 無鎖并發訪問
著色指針(Colored Pointer)
Colored Pointer,染色指針是一種讓指針存儲額外信息的技術。我們知道在64位操作系統里,一個內存的地址總共64位,但是受限于實際物理內存的大小,我們其實并不是真正的使用所有64位。這里如果小伙伴了解linux的虛擬內存管理會很好理解,我這里大概解釋一下。我們平時所說操作系統的“物理內存地址“并不是真正的”物理內存地址”,也就是說,并不是物理上,內存顆粒對應的地址。而是操作系統為我們虛擬的一個“虛擬地址”,這個技術被稱為虛擬內存管理。虛擬內存基本在所有的linux服務器上都有使用,除了少部分嵌入式設備,因為內存太小不需要使用這種技術。在虛擬內存的幫助下,我們可以做到兩個虛擬內存地址對應一個真實的物理地址。
對于JVM來說,一個對象的地址只使用前42位,而第43-46位用來存儲額外的信息,即GC對象處于ZGC那個階段。只使用46位的客觀原因是linux系統只支持46位的物理地址空間,即64T的內存,如果一定想要使用更大的內存,需要linux額外的設置。但是這個內存設置在主流的服務器上都夠用了。
在引用地址的劃分上,對象引用第43位表示marked0標記,44位marked1標記,45位remapped標記,46位finalizable標記。指針染色就是給對應的位置為1,當然這三個位同一個時間只能有一個位生效。這些標記分別表示對象處于GC的那個階段里。在下面ZGC的詳細過程里我們會介紹染色指針怎么幫助GC的。指針的引用地址在各個標記之間切換也被稱為指針的自愈。
-
Java Heap 的 4TB 是物理地址范圍:
-
Java Heap 的 4TB 由 0~41 位直接映射的物理地址決定,與 M0/M1 的虛擬地址空間無關。
-
M0/M1 的虛擬地址空間通過高位元數據擴展,但物理內存仍受限于硬件和操作系統。
-
5. 實際地址轉換示例
假設物理內存地址為 0xDEADBEEF
(42 位足夠表示):
-
在 M0 中的虛擬地址:
高位元數據(0000) | 物理地址(0xDEADBEEF) => 0x0000DEADBEEF...
-
在 M1 中的虛擬地址:
高位元數據(0001) | 物理地址(0xDEADBEEF) => 0x0001DEADBEEF...
-
切換地址空間:
-
通過原子操作將默認元數據從
0000
改為0001
,所有新訪問自動重定向到 M1。
-
核心設計:
-
高位存儲元數據: ZGC 將 64 位指針的高 4 位(第 42~45 位)用作元數據標記,而非傳統對象頭存儲存活狀態。
位范圍 用途 42~45 (4 bits) 對象存活狀態(標記、轉移階段等) 0~41 實際物理地址 -
標記位含義:
-
-
虛擬地址空間劃分: ZGC 將 64 位地址空間劃分為多個子空間,同一物理內存對應三個虛擬地址:
虛擬地址空間 | 地址范圍 | 作用 |
M0 | [4TB, 8TB) | 存放當前活躍對象的最新地址 |
M1 | [8TB, 12TB) | 存放對象轉移后的新地址 |
Remapped | [16TB, 20TB) | 用于地址重定向(空間換時間) |
ZGC中讀屏障的代碼作用:
在對象標記和轉移過程中,用于確定對象的引用地址是否滿足條件,并作出相應動作。
接下來詳細介紹ZGC一次垃圾回收周期中地址視圖的切換過程:
-
初始化:ZGC初始化之后,整個內存空間的地址視圖被設置為Remapped。程序正常運行,在內存中分配對象,滿足一定條件后垃圾回收啟動,此時進入標記階段。
-
并發標記階段:第一次進入標記階段時視圖為M0,如果對象被GC標記線程或者應用線程訪問過,那么就將對象的地址視圖從Remapped調整為M0。所以,在標記階段結束之后,對象的地址要么是M0視圖,要么是Remapped。如果對象的地址是M0視圖,那么說明對象是活躍的;如果對象的地址是Remapped視圖,說明對象是不活躍的。
-
重新標記(Remark)
這個階段是處理一些并發標記階段未處理完的任務(少量STW,控制在1ms內)如果沒處理完還會再次并發標 記,這里其實主要是解決三色標計算法中的漏標的問題,即白色對象被黑色對象持有的問題。并發標記階段發 生 引用更改的對象會被記錄下來(觸發讀屏障就會記錄),在這個階段標記引用被更改的對象
-
并發預備重分配(Concurrent Prepare for Relocate)
??這一步主要是為了之后的遷移做準備,這一步主要是處理軟引用,弱引用,虛引用對象,以及重置page的Forwarding table, 收集待回收的page信息到Relocation Set
??Forwarding table是記錄對象被遷移后的新舊引用的映射表。Relocation Set是存放記錄需要回收的存活頁集合。這個階段ZGC會掃描所有的page,將需要遷移的page信息存儲到Relocation Set,這一點和G1很不一樣,G1是只選擇部分需要回收的Region。在記錄page信息的同時還會初始化page的Forwarding table,記錄下每個page里有哪些需要遷移的對象。這一步耗時很長,因為是全量掃描所有的page,但是因為是和用戶線程并發運行的,所以并不會STW,而且對比G1,還省去了維護RSet和SATB的成本。
-
初始遷移(Relocate Start)
??這個階段是為了找出所有GC Roots直接可達的對象,并且切換good mask到remapped,這一步是STW的。這里注意一個問題,被GC Roots直接引用的對象可能需要遷移。如果需要,則會將該對象復制到新的page里,并且修正GC Roots指向本對象的指針,這個過程就是“指針的自愈”。當然這不是重點重點是切換good mask.
-
并發轉移階段:標記結束后就進入轉移階段,此時地址視圖再次被設置為Remapped。如果對象被GC轉移線程或者應用線程訪問過,那么就將對象的地址視圖從M0調整為Remapped。
其實,在標記階段存在兩個地址視圖M0和M1,上面的過程顯示只用了一個地址視圖。之所以設計成兩個,是為了區別前一次標記和當前標記。也即,第二次進入并發標記階段后,地址視圖調整為M1,而非M0。
著色指針和讀屏障技術不僅應用在并發轉移階段,還應用在并發標記階段:將對象設置為已標記,傳統的垃圾回收器需要進行一次內存訪問,并將對象存活信息放在對象頭中;而在ZGC中,只需要設置指針地址的第42~45位即可,并且因為是寄存器訪問,所以速度比訪問內存更快
ZGC的優勢很明顯,幾乎全程并發的回收過程帶來了無與倫比的低暫停時間,這也是ZGC的設計思路。低暫停時間加上JAVA本身的支持高并發的特點,假以時日ZGC將來一定是能在服務器領域的展現它大殺器級別的威力。但是為了達到這個設計目標,ZGC其實也犧牲了一些東西,比如吞吐量以及在JDK21之前ZGC不分代
調優案例(參考美團技術文章)
案例一:秒殺活動中流量突增,出現性能毛刺
(1)開啟”基于固定時間間隔“的GC觸發機制:-XX:ZCollectionInterval。比如調整為5秒,甚至更短。
(2)增大修正系數-XX:ZAllocationSpikeTolerance,更早觸發GC。ZGC采用正態分布模型預測內存分配速率,模型修正系數ZAllocationSpikeTolerance默認值為2,值越大,越早的觸發GC,Zeus中所有集群設置的是5。
案例二:壓測時,流量逐漸增大到一定程度后,出現性能毛刺
增大-XX:ConcGCThreads, 加快并發標記和回收速度。ConcGCThreads默認值是核數的1/8,8核機器,默認值是1。該參數影響系統吞吐,如果GC間隔時間大于GC周期,不建議調整該參數。
案例三: 單次GC停頓時間30ms,與預期停頓10ms左右有較大差距
升級Aviator組件版本,避免生成多余的ClassLoader。
案例四:服務啟動后,運行時間越長,單次GC時間越長,重啟后恢復
通過業務優化解決,刪除不需要執行的Aviator表達式,從而避免了大量Aviator方法進入CodeCache中。