背景 TLDR
垃圾回收器的暫停問題對實時響應要求較高的服務來說,一直是個痛點, CMS和G1等主流垃圾回收器的數十毫秒乃至上百毫秒的暫停時間相當致命。 此外,調優門檻也相對較高,需要對垃圾回收器的內部機制有一定的瞭解,才能夠進行有效的調優。 隨著ZGC的出現, 使得這一痛點徹底解決, ZGC 最初在 JDK 11 中作為實驗性功能引入,並在 JDK 15 中宣佈為生產就緒, 由於JDK17纔是比較正式提供給大眾實用的LTS支援版本,而且一部分公司已經在使用,所以本文力推JDK17。
ZGC 作為一款低延遲垃圾收集器,旨在滿足以下目標:
8MB到16TB的堆大小支援
10ms最大GC暫時
最糟糕的情況下吞吐量會降低15%(實測,如果引數配置的問題可能更糟, 官方這個稍微吹牛了點, 說實話就是用CPU換GC時間,也沒有那麼高大上)
升級JDK17的不可拒絕的理由
低延遲的業務需求,毫秒級耗時的GC
據美團的開發說:
在Zeus服務不同叢集中,ZGC在低延遲(TP999 < 200ms)場景中收益較大:
TP999:下降12~142ms,下降幅度18%~74%。
TP99:下降5~28ms,下降幅度10%~47%。
可以忽略的升級JDK17的理由
新版的SpringBoot 官方最低支援 JDK17,想使用新Spring版本,就得升級
JIT 編譯器的增強
JDK 17 中的新功能,例如 Sealed 類、Pattern Matching、Records 等
升級到 JDK 17 可以獲得更好的安全性,包括修復的漏洞和強化的安全機制
適用場景
閘道器服務
Web API
暫不推薦場景: 定時任務、批次任務、高CPU密集型應用
升級前後對比
話不多說,先看效果
環境:
CPU: 4c Mem: 6GB
G1引數:
-Xmx3500m -Xms3500m -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:G1ReservePercent=10 -XX:ConcGCThreads=2 -XX:ParallelGCThreads=5 -XX:G1HeapRegionSize=16m -XX:MaxTenuringThreshold=14 -XX:SurvivorRatio=8
ZGC引數:
--add-opens=java.base/java.lang=ALL-UNNAMED -Xms3500m -Xmx3500m -XX:ReservedCodeCacheSize=256m -XX:InitialCodeCacheSize=256m -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -XX:ConcGCThreads=1 -XX:ParallelGCThreads=3 -XX:ZCollectionInterval=60 -XX:ZAllocationSpikeTolerance=4 -XX:+UnlockDiagnosticVMOptions -XX:-ZProactive -Xlog:safepoint,classhisto*=trace,age*,gc*=info:file=/opt/gc-%t.log:time,tid,tags:filecount=5,filesize=50m
上述兩個引數,均已經在生產環境實驗過, 生產環境的機器是單機擁有1500業務tps的機器
GC耗時對比
從上圖可見, GC耗時是有著質的區別的,這個區別是你用CMS、Parallel GC、 G1 等嘔心瀝血也調校不出來的
這麼短的GC, 可以保證,應用因為JVM層面的卡頓都保持在1ms 以內, 這也是為啥說這點纔是不能拒絕的理由
CPU使用對比
從CPU使用上看, JDK17 相同的程式碼, 比JDK8要高出10 ~ 20%
升級方法
1. JDK選擇或安裝
使用JDK17前必須要安裝JDK17, 對於不同的Linux發行版或者作業系統安裝方法各不相同, 下面給出了一些樣例, 僅供參考。
# ubuntu 安裝jdk17 sudo apt install openjdk-17-jdk # docker 基礎映象 docker pull openjdk:17-slim docker pull openjdk:17-jdk-oraclelinux7
FROM openjdk:17-slim
2. JVM 引數調整
有了JDK17後,你已經具備了讓你的Java程式執行在JDK17上的基本條件了,下一步便是配置Jvm 引數如下(有需要的話,可以自行把換行整理下):
--add-opens=java.base/java.lang=ALL-UNNAMED \ -Xms1500m -Xmx1500m \ -XX:ReservedCodeCacheSize=256m \ -XX:InitialCodeCacheSize=256m \ -XX:+UnlockExperimentalVMOptions \ -XX:+UseZGC \ -XX:ConcGCThreads=1 -XX:ParallelGCThreads=2 \ -XX:ZCollectionInterval=30 -XX:ZAllocationSpikeTolerance=5 \ -XX:+UnlockDiagnosticVMOptions -XX:-ZProactive \ -Xlog:safepoint,classhisto*=trace,age*,gc*=info:file=/opt/gc-%t.log:time,tid,tags:filecount=5,filesize=50m \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/opt/errorDump.hprof
引數釋義
引數名 | 引數含義 |
--add-opens |
JDK9模組化後,導致javabase下面的一些基礎包訪問許可權受阻, 需要開放訪問, 預設 設定java.base/java.lang=ALL-UNNAMED 即可 |
-Xms1500m -Xmx1500m |
堆記憶體設定,非常常見,不解釋 |
-XX:ReservedCodeCacheSize=256m |
JIT編譯的程式碼都放在CodeCache中,一般服務64m或128m就已經足夠 |
-XX:InitialCodeCacheSize=256m |
初始code cache大小 |
-XX:+UnlockExperimentalVMOptions |
開啟實驗性質的JVM選項,開啟可以解鎖更多的JVM設定的能力 |
-XX:+UseZGC |
使用ZGC |
-XX:ConcGCThreads=1 |
併發回收垃圾的執行緒。預設是總核數的12.5%,8核CPU預設是1。調大後GC變快,但會多佔用程式執行時的CPU資源 |
-XX:ParallelGCThreads=2 |
STW階段使用執行緒數,預設是總核數的60% |
-XX:ZCollectionInterval=30 |
GC週期之間的最大間隔(單位秒) |
-XX:ZAllocationSpikeTolerance=5 |
增大修正係數-XX:ZAllocationSpikeTolerance,更早觸發GC。ZGC採用正態分佈模型預測記憶體分配速率,模型修正係數ZAllocationSpikeTolerance預設值為2 |
-XX:+UnlockDiagnosticVMOptions -XX:-ZProactive |
是否啟用主動回收,預設開啟,這裏的配置表示關閉 |
-Xlog:safepoint,classhisto*=trace,age*,gc*=info:file=/opt/gc-%t.log:time,tid,tags:filecount=5,filesize=50m |
GC日誌設定 |
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/errorDump.hprof |
當發生OOM的時候HEAP DUMP 及其配置 |