前言
在Java開發中,垃圾收集器的選擇對系統性能有著致命的影響。Java 8后,雖然G1 GC成為默認,但是它在延遲性控制上仍有限。ZGC作為最新一代高性能低延遲垃圾收集器,解決了CMS和G1在延遲、垃圾堆容量和吞吐量方面的重大突破。本文將完整給出ZGC的技術原理和實際應用,幫助您做出最適合應用場景的GC選型。
Java中的垃圾收集概述
垃圾收集的意義
Java采用自動內存管理(GC),幫助開發者自動處理不再使用的對象內存。GC的目標是在盡量減少延遲的同時,回收無用對象,維持系統的穩定運行和內存調度。
當前垃圾收集系統培勝于 "根集" 分析,根集為程序可達對象的集合,GC進行探游,找出可達對象,其余則可以處理成垃圾。
Java堆內存分區
Java堆內存通常分為兩大區域:
Young Generation (年輕代):新生成的對象,較簡單。內部分為Eden和Survivor S0/S1。
Old Generation (老年代):清除盡Young GC后仍然存活的對象,常應用于Full GC。
有些GC器同時使用總緩存(Metaspace)、堆外內存區域、選擇性老年代等特殊區域。
Java垃圾收集器的演進
在深入了解ZGC之前,我們需要先回顧Java中垃圾收集器的發展歷程,特別是ZGC推出之前在Java 8及更早版本中常見的幾種GC方式。這不僅幫助我們理解ZGC為何而生,也為我們提供比較其優劣的視角。
Java 8中的主要垃圾收集器
Java 8中默認的垃圾收集器是Parallel GC,但在很多中大型項目中,開發者常常會根據不同的業務需求切換為CMS(Concurrent Mark-Sweep)或者G1(Garbage First)。下面我們來分別了解這些GC方式的基本特點與運行機制。
1. Serial GC
適用于單核處理器或者內存較小的客戶端應用。
特點:串行執行,GC過程中STW(Stop-The-World)時間較長。
優點:實現簡單,適用于內存小、線程少的環境。
缺點:不適合服務器端或多線程環境。
// 啟用Serial GC
java -XX:+UseSerialGC -Xms512m -Xmx512m MyApp
2. Parallel GC(吞吐量優先GC)
也稱為吞吐量GC,追求最大程度的吞吐量,適用于批處理和計算密集型任務。
特點:多個GC線程并行執行,仍會發生Stop-The-World。
優點:高吞吐、GC時間相對較短。
缺點:GC期間程序線程全部暫停。
// 啟用Parallel GC
java -XX:+UseParallelGC -Xms1g -Xmx1g MyApp
3. CMS GC(并發標記清除)
目標是減少GC對程序運行的影響,引入并發階段。
特點:多階段GC流程,包括初始標記、并發標記、重新標記、并發清除。
優點:在標記和清除階段大部分操作可并發,適合響應時間敏感型應用。
缺點:內存碎片化嚴重,標記過程復雜。
// 啟用CMS GC
java -XX:+UseConcMarkSweepGC -Xms2g -Xmx2g MyApp
4. G1 GC(Garbage First)
G1是在Java 8中被引入并逐漸替代CMS的收集器,強調可預測的停頓時間。
特點:堆被分成多個小區域(Region),混合收集老年代和年輕代。
優點:減少Full GC頻率,支持大內存,延遲控制能力較CMS強。
缺點:在低延遲場景仍有不可預測的長時間停頓。
// 啟用G1 GC
java -XX:+UseG1GC -Xms4g -Xmx4g MyApp
各GC對比總結
GC 類型 | 并發能力 | 停頓時間 | 吞吐量 | 內存使用效率 | 是否碎片整理 |
---|---|---|---|---|---|
Serial | 無 | 高 | 中 | 高 | 是 |
Parallel | 否 | 中等 | 高 | 中 | 是 |
CMS | 是 | 低 | 中 | 低(有碎片) | 否 |
G1 | 是 | 中到低 | 中 | 高 | 是 |
從上表中可以看出,雖然CMS和G1 GC在延遲方面取得了一定進展,但仍存在以下痛點:
Full GC影響嚴重:尤其是在老年代清理時,仍需STW,造成業務請求中斷。
大堆內存支持不佳:CMS在大堆場景(數十GB以上)容易產生碎片,甚至OOM。
標記和清理效率有限:并發過程開銷大,回收速度不夠理想。
因此,為了進一步降低延遲、提升大內存環境下的GC性能,ZGC應運而生,尤其在Java 11之后成為低延遲場景的新寵。
ZGC概述
ZGC(Z Garbage Collector)是Java平臺自Java 11起引入的一種可擴展、低延遲、并發型垃圾收集器,旨在為大堆內存場景下的Java應用提供極低的GC暫停時間(最大不超過10ms),同時保持高吞吐量。
在ZGC出現前,雖然G1 GC已實現了對延遲控制的初步優化,但在某些實時性要求極高的系統中(如金融撮合引擎、大型電商、在線游戲服務器等),它仍然無法完全滿足毫秒級停頓時間的需求。ZGC正是為此類場景設計。
核心目標
ZGC的主要設計目標如下:
暫停時間不超過10ms(與堆大小無關)
支持超大堆內存(最大支持16TB)
并發回收、并發壓縮
低吞吐量損失
低內存碎片率
這些特性使得ZGC非常適用于以下應用場景:
低延遲應用(例如在線交易系統)
大型數據處理(大內存服務端)
響應時間敏感的分布式系統
Java版本支持情況
ZGC最早以實驗性功能形式在JDK 11中引入,之后不斷發展完善:
Java 版本 | 狀態 | 啟用方式 |
---|---|---|
JDK 11 | 實驗性 | -XX:+UnlockExperimentalVMOptions -XX:+UseZGC |
JDK 15 | 正式支持 | -XX:+UseZGC |
JDK 17 | 長期支持(LTS) | -XX:+UseZGC |
JDK 21 | 引入Generational ZGC | -XX:+UseZGC (自動使用代際ZGC) |
注意:ZGC不支持Java 8。在JDK 8中,建議使用G1 GC作為替代方案來控制延遲,但G1在最小停頓時間方面遠不如ZGC。
如何啟用ZGC
在JDK 11中啟用ZGC需顯式解鎖實驗性選項:
java \-XX:+UnlockExperimentalVMOptions \-XX:+UseZGC \-Xmx8g \-Xms8g \-jar myapp.jar
從JDK 15起,ZGC已成為正式功能,無需再解鎖實驗性選項:
java \-XX:+UseZGC \-Xmx16g \-Xms16g \-jar myapp.jar
建議在中大型內存(如4GB以上)下使用ZGC以體現其優勢。
ZGC命名的由來
ZGC中的"Z"并沒有官方明確解釋,但社區中普遍認為其含義為:
Zero Pause GC(零停頓GC)
或者表示最終GC的終極目標(The last GC you'll ever need)
ZGC架構深度分析
ZGC之所以能夠實現低于10ms的暫停時間,離不開其創新性的內部架構設計。ZGC徹底顛覆了以往垃圾收集器在內存布局和對象訪問上的方式,采用以下關鍵技術:
區域化堆(Region-based Heap)
彩色指針(Colored Pointers)
加載屏障(Load Barriers)
并發回收機制
本節將逐一詳細講解這些核心模塊。本部分為第一部分,重點解析:
1. 區域化堆(Region-based Heap)
傳統GC如CMS和Parallel GC使用固定大小的堆區域分代(如Eden區、Survivor區、Old區),但ZGC則摒棄了這種固定代分區方式,采用區域化(Region-based)堆布局。
ZGC的堆被動態劃分成一塊塊的邏輯小區域(region),每個區域最小為2MB,最大可以根據配置擴展。這些區域根據用途被分類如下:
Small Object Space:存儲小對象,一般小于256KB。
Large Object Space:存儲大對象,單個對象跨多個區域。
Remapped Space:用于搬遷中的對象區域(relocation)。
ZGC的region是非連續、非固定映射的,具有以下優勢:
堆可動態增長和收縮,極大提升內存使用彈性。
對不同區域類型可采用不同的壓縮或回收策略。
支持并發搬遷與并發壓縮,減少碎片和延遲。
示例:
// 模擬大對象分配時,ZGC自動從 Large Object Space 中分配區域
byte[] largeArray = new byte[10 * 1024 * 1024]; // 10MB 數組
ZGC可快速在Large Object Space中定位空閑Region,支持高并發申請。
2. 彩色指針(Colored Pointers)
ZGC使用的一項革命性技術是彩色指針,即將對象引用的高位用于標記GC狀態信息。這種做法打破了傳統將引用與元數據分離的限制。
ZGC的對象指針是64位,但由于現代操作系統通常只用低48位尋址,ZGC利用高位中的幾位嵌入顏色信息:
位段 | 含義 |
---|---|
0-47 | 實際地址 |
48 | Finalizable標記位 |
49 | Remapped標記位 |
50 | Marked標記位 |
51 | Load Barrier位 |
通過這種設計,ZGC可以在訪問對象指針的瞬間就獲知其GC狀態,而無需查找外部元數據結構,大大提升了并發訪問的性能。
優點包括:
減少GC元信息結構依賴
提升GC期間并發可達性分析速度
降低堆碎片率和對象搬遷時的同步成本
示例說明:
Object ref = obj; // ref 實際上是帶顏色標記的指針
開發者無需干預,ZGC自動對這些指針做屏障處理和位標記解碼。
3. 加載屏障(Load Barrier)
**加載屏障(Load Barrier)**是ZGC最獨特的技術之一。它在每次訪問Java對象引用時自動觸發,用于處理對象在搬遷過程中的一致性問題。
加載屏障的主要職責是:
判斷引用對象是否已被搬遷(relocated)
如果是,執行指針修復(pointer remapping)
保證所有線程訪問到的是最新的對象地址
ZGC加載屏障是在JVM層面插入的,對開發者完全透明,不需要修改應用代碼。其實現通常依賴CPU原語(如內存屏障)結合內聯匯編,實現高效的指針判斷與更新。
加載屏障的邏輯類似如下偽代碼:
Object ref = load(o);
if (ref has relocation flag) {ref = remap(ref);
}
return ref;
優勢:
極低延遲:加載屏障可與普通對象訪問融合,不增加明顯開銷
并發友好:允許對象在不暫停應用線程的前提下完成搬遷
精確控制:每次讀取都能精準判斷是否需要修復
真實案例場景:
List<Person> people = ...;
for (Person p : people) {// 訪問 p.getName() 時會觸發 Load Barrier 檢查其對象是否已搬遷System.out.println(p.getName());
}
即使此時ZGC正在并發地搬遷 Person
對象,也不會阻塞當前線程讀取。
4. 并發回收機制
ZGC之所以能將GC暫停控制在10ms以內,根本在于其極致的并發垃圾回收機制。它幾乎將所有GC階段轉為并發執行,避免了傳統GC中長時間的"Stop-The-World"。
ZGC的垃圾回收周期包含如下階段:
階段 | 是否并發 | 描述 |
---|---|---|
初始標記(Pause Mark Start) | 否 | 極短暫停,標記GC Root |
并發標記(Concurrent Mark) | 是 | 并發遍歷整個堆,標記存活對象 |
并發重定位準備(Concurrent Prepare Relocate) | 是 | 選擇要搬遷的對象 |
暫停重定位開始(Pause Relocate Start) | 否 | 短暫停,開啟重定位 |
并發搬遷(Concurrent Relocate) | 是 | 將活躍對象搬遷至新區域 |
并發重映射(Concurrent Remap) | 是 | 更新所有引用為新地址(結合Load Barrier) |
其中,兩個短暫停階段(初始標記與重定位開始)通常耗時都小于2ms。
ZGC的設計核心是將搬遷(對象復制)也并發完成,這在傳統GC中幾乎是不可想象的。
示例流程圖(文字描述):
GC線程開始并發標記階段,與應用線程并行運行。
找到垃圾對象集合后,選擇部分區域進行回收。
搬遷對象至新區域,由多個線程協作完成,應用線程繼續運行。
通過加載屏障和指針重映射,確保應用訪問到的是新地址。
這種模式極大減少了STW(Stop-The-World)對應用性能的影響。
優勢總結:
幾乎全程并發執行
極低暫停時間(<10ms)
支持TB級堆空間的低延遲回收
精細控制搬遷單元,避免大塊復制阻塞
ZGC工作原理
ZGC的基本目標是在實現大內存空間支持下,并俗降低GC暫停時間,盡量將GC各階段轉為并發執行。下面將以ZGC一次完整GC周期為線程,分段解析其工作流程:
1. GC觸發
ZGC與其他GC一樣,通過內存占用分析來觸發GC。其GC觸發可能原因包括:
內存占用超過閥值
對象分配失敗
手動調用
System.gc()
在GC觸發后,ZGC進入一次完整的GC周期。
2. 初始標記 (Pause Mark Start)
這是ZGC兩個短暫停階段之一,將GC Root(如程序棧、靜態對象、JNI指針) 加入標記集合。
特點:
更新系統中所有根引用
更新開始標記的標志位(帶有顏色的指針)
優化後通常耗時<1ms
3. 并發標記 (Concurrent Mark)
將基于標記集合的引用給所有可達對象進行并發添加。此階段與應用程序同時運行,不需要暫停。
內部機制:
利用帶顏色指針判定對象是否已標記
培子線程分布標記任務,支持核心級并發
并行識別、合并圖結構
// 類似于每個對象被標記為活躍時,就會追蹤其引用
if (!isMarked(obj)) {mark(obj);for (Object ref : obj.getReferences()) {mark(ref);}
}
4. 并發轉移準備 (Concurrent Prepare Relocate)
此階段分析哪些Region需要轉移,通常選擇廢物比例高的Region,盡量減少拷貝量,提高性能。
標記結束后,定義要移動的Region集合
與應用程序并行
5. 移動開始暫停 (Pause Relocate Start)
為了保證移動階段的一致性,需要簡短地暫停一下,切換GC狀態,啟用轉移。
暫停平均耗時也很短(<2ms)
進行新的Region空間創建
6. 并發對象移動 (Concurrent Relocate)
ZGC使用并發線程將活躍對象轉移到新的Region。這些操作與應用程序同時進行,合作Load Barrier確保引用不算錯。
// Load Barrier檢測到指針已移動
if (isForwarded(ptr)) {ptr = loadForwardingPointer(ptr); // 修復指針
}
7. 并發重映 (Concurrent Remap)
在應用運行過程中,某些指針可能還未被修復,此階段將通過并發線程把還未被更新的指針重新映射。
重映是指針修復的最后階段
確保所有引用指向正確對象
8. GC結束
當所有移動完成、指針已更新后,GC周期結束,釋放被固定重映的老Region,新Region補入。
ZGC與Java 8中垃圾收集器的對比
在Java 8中,常用的幾種垃圾收集器包括Serial GC、Parallel GC、CMS(Concurrent Mark-Sweep)以及G1(Garbage First)GC。這些收集器在當時各有優劣,而ZGC自Java 11引入后,徹底改變了GC在延遲敏感場景下的表現。本節將ZGC與Java 8時代代表性的垃圾收集器——G1、CMS等進行對比,從多個維度全面展示ZGC的優勢與局限。
1. 暫停時間對比
收集器 | 暫停類型 | 最佳暫停時間 | 典型暫停時間 | 最差暫停時間 |
---|---|---|---|---|
Serial | Stop-the-World | 10ms - 數百ms | 數十ms - 秒級 | 秒級 |
Parallel | Stop-the-World | 數十ms | 數百ms | 數秒 |
CMS | 并發標記 + STW | 幾十ms | 數百ms | 可能超過1s(碎片整理) |
G1 | 分區化,部分并發 | 低于200ms(可設定) | 50ms - 200ms | 秒級(Full GC) |
ZGC | 幾乎全并發 | <1ms | <10ms | 通常不超過10ms |
ZGC的暫停時間控制極其優秀,甚至在TB級堆上依然穩定控制在10ms以內。
2. 吞吐量與堆規模支持
收集器 | 最大支持堆大小 | 吞吐能力 |
Serial | 小于8GB | 較低 |
Parallel | 幾十GB | 高 |
CMS | 通常推薦<100GB | 中高 |
G1 | 理論上可到數百GB | 中高(根據Region粒度變化) |
ZGC | 實際支持高達數TB | 高(并發、分層) |
ZGC特別適用于超大堆應用,如大數據平臺、實時分析引擎、AI在線推理服務等。
3. 并發能力
收集器 | 并發標記 | 并發清理/壓縮 | 對應用線程干擾 |
Serial | 否 | 否 | 高 |
Parallel | 否 | 否 | 高 |
CMS | 是 | 部分并發 | 中 |
G1 | 是 | 否(壓縮是STW) | 中 |
ZGC | 是 | 是(全階段并發) | 極低 |
ZGC是真正意義上"全階段并發"的垃圾收集器,大幅減小GC對響應時間的干擾。
4. 內存碎片處理
CMS 是非壓縮的,容易出現內存碎片,導致分配失敗。
G1 雖然分區化,但壓縮仍需STW,內存整理成本高。
ZGC 利用并發搬遷機制,可以在線完成對象壓縮,極少碎片產生。
// G1碎片整理常常需要Stop-the-World:
-XX:+UseG1GC
-XX:+G1HeapRegionSize=8m
// 遇到Old區不足可能引發 Full GC 暫停
5. 部署與兼容性
收集器 | Java版本支持 | 啟用方式 |
CMS | Java 8 - Java 14(后移除) | -XX:+UseConcMarkSweepGC |
G1 | Java 7+ | 默認GC(Java 9+) |
ZGC | Java 11+(JDK 15轉為生產) |
-XX:+UnlockExperimentalVMOptions
-XX:+UseZGC
``` |ZGC從JDK 15起被標記為生產可用,推薦用于延遲敏感、堆空間巨大的現代系統。### 6. 代碼透明度與可維護性ZGC對開發者完全透明,不需要特殊編碼配合,不影響業務邏輯。```java
public class User {private String name;private Address address;
}// 正常使用,不需要關注GC過程:
User u = new User();
u.setName("張三");
相比之下,CMS可能因碎片化或過時參數帶來配置難度,而G1對GC參數的調優要求較高。
總結:為何選ZGC?
ZGC在以下場景中極具優勢:
低延遲要求:如金融交易撮合系統、在線推薦、游戲服務端
大內存平臺:如TB級堆數據倉庫、機器學習推理服務、海量會話保持
高并發業務:如大型API網關、消息中間件
同時,ZGC的易用性(代碼透明、自動壓縮、穩定暫停時間)也大大降低了運維與開發門檻,是Java未來GC的核心發展方向之一。
ZGC性能調優指南
ZGC雖然擁有優秀的默認性能表現,但在特定業務場景中,通過合理調優可以進一步提升其效率,降低資源占用,增強服務穩定性。本節將圍繞ZGC的參數設置、性能監控、常見問題應對策略進行系統講解。
一、ZGC啟用與基礎配置
ZGC需Java 11及以上版本支持,啟用ZGC基本參數如下:
# 啟用ZGC(Java 11中仍為實驗特性)
-XX:+UnlockExperimentalVMOptions
-XX:+UseZGC# 示例:設置最大堆、初始堆大小
-Xmx16g
-Xms16g
在JDK 15及以上版本,無需再解鎖實驗選項,可直接使用 -XX:+UseZGC
。
二、核心調優參數解析
1. 堆空間設置
ZGC不像G1那樣依賴分區大小調優,它的核心是Region自動管理。因此主要關注以下兩個參數:
-Xmx16g # 最大堆內存
-Xms16g # 初始堆內存
建議:ZGC在大堆(如>8G)下性能更優,最好將 -Xmx 與 -Xms 設為相同,避免運行時堆調整。
2. GC線程數控制
-XX:ConcGCThreads=N # 控制并發GC線程數量
-XX:ParallelGCThreads=N # 初始標記和對象拷貝時的并行線程數
ZGC自動選擇線程數,但在高并發系統中,如需控制資源消耗可手動設定。
3. 啟用 NUMA 感知(多核性能優化)
-XX:+UseNUMA
在多Socket架構服務器上建議啟用,提升跨節點堆訪問性能。
4. 禁用透明大頁(降低TLB抖動)
-XX:+UseTransparentHugePages=false
可避免ZGC在大型對象分配中引發頻繁頁表轉換開銷。
三、ZGC特有診斷與追蹤參數
ZGC支持詳細的垃圾回收日志輸出,有助于觀察其行為與性能:
-Xlog:gc*,safepoint:file=gc.log:time,uptime,level,tags
樣例輸出:
[2.344s][info][gc,start] GC(0) Pause Mark Start
[2.344s][info][gc] GC(0) Pause Mark Start 0.415ms
[2.345s][info][gc,start] GC(0) Concurrent Mark
[2.567s][info][gc] GC(0) Concurrent Mark 222.187ms
...
通過這些日志,可判斷暫停時間是否穩定,GC是否頻繁觸發等信息。
四、常見調優策略
場景 | 調優建議 |
---|---|
吞吐不足 | 提升GC線程數,增加CPU核心數 |
GC頻繁 | 檢查內存是否足夠,提升 -Xmx 值 |
暫停波動大 | 檢查是否開啟了大頁,是否存在頻繁Full GC |
CPU占用高 | 適當限制 GC 并發線程數 |
堆未滿就GC | 檢查是否被顯式調用 System.gc(),避免誤觸發 |
五、結合容器環境的參數配置建議
在Kubernetes、Docker等容器中運行時,應注意:
-XX:+UseContainerSupport # 啟用容器資源感知(JDK 10+ 默認開啟)
-XX:MaxRAMPercentage=80.0 # 最大可用內存占比(替代傳統的 -Xmx)
這樣可確保ZGC在容器內合理管理資源,不會溢出宿主機。