我所使用的生產 Java 17 啟動引數

乾貨滿滿張雜湊 發表於 2022-06-15
Java

JVM 引數升級提示工具:jacoline.dev/inspect
JVM 引數詞典:chriswhocodes.com
Revolut(英國支付巨頭)升級 Java 17 實戰:https://www.bilibili.com/video/bv1SA4y1d7sZ

目前正常微服務綜合記憶體佔用+延遲+吞吐量,還是 G1 更優秀。但是如果你的微服務本身壓力沒到機器極限,要求延遲低,那麼 ZGC 最好。如果你是實現資料庫那樣的需求(大量快取物件,即長時間生存物件,老年代很大,並且還會可能分配大於區域的物件),那麼必須使用 ZGC。

使用 G1GC 啟動引數:

-XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:-OmitStackTraceInFastThrow -Xlog:gc*=debug:file=${LOG_PATH}/gc%t.log:utctime,level,tags:filecount=50,filesize=100M -Xlog:jit+compilation=info:file=${LOG_PATH}/jit_compile%t.log:utctime,level,tags:filecount=10,filesize=10M -Xlog:safepoint=debug:file=${LOG_PATH}/safepoint%t.log:utctime,level,tags:filecount=10,filesize=10M -Dfile.encoding=UTF-8 -Djava.security.egd=file:/dev/./urandom -Dnetworkaddress.cache.ttl=10 -Xms2048m -Xmx2048m -Xmn1280m -Xss512k -XX:MaxDirectMemorySize=1024m -XX:MetaspaceSize=384m -XX:ReservedCodeCacheSize=256m -XX:+DisableExplicitGC -XX:MaxGCPauseMillis=50 -XX:-UseBiasedLocking -XX:GuaranteedSafepointInterval=0 -XX:+UseCountedLoopSafepoints -XX:StartFlightRecording=disk=true,maxsize=4096m,maxage=3d -XX:FlightRecorderOptions=maxchunksize=128m --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.base/java.time=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/jdk.internal.access=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED

使用 ZGC 啟動引數:

-XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:-OmitStackTraceInFastThrow -Xlog:gc*=debug:file=${LOG_PATH}/gc%t.log:utctime,level,tags:filecount=50,filesize=100M -Xlog:jit+compilation=info:file=${LOG_PATH}/jit_compile%t.log:utctime,level,tags:filecount=10,filesize=10M -Xlog:safepoint=debug:file=${LOG_PATH}/safepoint%t.log:utctime,level,tags:filecount=10,filesize=10M -Dfile.encoding=UTF-8 -Djava.security.egd=file:/dev/./urandom -Dnetworkaddress.cache.ttl=10 -Xms2048m -Xmx2048m -Xmn1280m -Xss512k -XX:MaxDirectMemorySize=1024m -XX:MetaspaceSize=384m -XX:ReservedCodeCacheSize=256m -XX:+DisableExplicitGC -XX:+UseZGC -XX:-UseBiasedLocking -XX:GuaranteedSafepointInterval=0 -XX:+UseCountedLoopSafepoints -XX:StartFlightRecording=disk=true,maxsize=4096m,maxage=3d -XX:FlightRecorderOptions=maxchunksize=128m --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.base/java.time=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/jdk.internal.access=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED

其中,需要做成環境變數外部可以配置的是:

  • -Xms2048m -Xmx2048m -Xmn1280m -Xss512k -XX:MaxDirectMemorySize=1024m -XX:MetaspaceSize=384m -XX:ReservedCodeCacheSize=256m 裡面的引數
  • -XX:StartFlightRecording=disk=true,maxsize=4096m,maxage=3d 其中的 4096m 以及 3d
  • -XX:MaxGCPauseMillis=50:這個只有使用 G1GC 的需要

JVM 日誌相關:

JVM 日誌配置請參考:https://zhuanlan.zhihu.com/p/111886882

  1. GC日誌:-Xlog:gc*=debug:file=${LOG_PATH}/gc%t.log:utctime,level,tags:filecount=50,filesize=100M
  2. JIT 編譯日誌:-Xlog:jit+compilation=info:file=${LOG_PATH}/jit_compile%t.log:utctime,level,tags:filecount=10,filesize=10M
  3. Safepoint 日誌:-Xlog:safepoint=debug:file=${LOG_PATH}/safepoint%t.log:utctime,level,tags:filecount=10,filesize=10M
  4. 關閉堆疊省略:這個只會省略 JDK 內部的異常,比如 NullPointerException 這種的:-XX:-OmitStackTraceInFastThrow,我們應用已經對於大量報錯的時候輸出大量堆疊導致效能壓力的優化,參考:https://zhuanlan.zhihu.com/p/428375711

系統屬性(環境變數)啟動引數:

  1. -Dfile.encoding=UTF-8:指定編碼為 UTF-8,其實 Java 18 之後預設編碼就是 UTF-8 了,這樣避免不同作業系統編譯帶來的差異(Windows 預設是 GB2312,Linux 預設是 UTF-8),參考:https://openjdk.java.net/jeps/400
  2. -Djava.security.egd=file:/dev/./urandom:更換 random 為 urandom 避免高併發加密證照通訊的時候的生成隨機數帶來的阻塞(例如高併發 https 請求,高併發 mysql 連線通訊),參考:https://zhuanlan.zhihu.com/p/259874076
  3. -Dnetworkaddress.cache.ttl=10:將 DNS 快取降低為 10s 過期,我們們 k8s 內部有很多通過域名解析的資源(通過 k8s 的 coreDNS),解析的 ip 可能會過期,漂移成新的 ip,預設的 30s 有點久,改成 10s,但是這會增加 coreDNS 的壓力。

記憶體控制相關:

以下需要做成可以在外部配置的環境變數

  1. 堆記憶體控制:-Xms2048m -Xmx2048m -Xmn1280m
  2. 執行緒棧大小控制:-Xss512k
  3. 直接記憶體(各種 Direct Buffer)大小控制:-XX:MaxDirectMemorySize=1024m
  4. 元空間控制:-XX:MetaspaceSize=384m
  5. JIT 即時編譯後(C1 C2 編譯器優化)的程式碼佔用記憶體:-XX:ReservedCodeCacheSize=256m

除了以上記憶體,JVM 還有其他記憶體佔用,無法通過顯示的配置限制,參考:https://www.zhihu.com/question/58943470/answer/2440458704

GC 控制相關:

通用引數:

  1. -XX:+DisableExplicitGC:關閉顯示 GC(System.gc()觸發的 FullGC),防止 netty 這種誤檢測記憶體洩漏顯示呼叫

G1GC 引數:

Java 9 之後預設 GC 就是 G1GC,所以不用顯示指定使用 G1GC

在 Java 14 之後 G1GC 有巨大突破,目前 Java 17 中已經不需要調非常複雜的引數了,可以只調整目標最大 STW(Stop-the-world) 時間來均衡 CPU 佔用,記憶體佔用與延遲。

  1. -XX:MaxGCPauseMillis=50:目標最大 STW(Stop-the-world) 時間,這個越小,GC 佔用 CPU 資源,佔用記憶體資源就越多,微服務吞吐量就越小,但是延遲低。這個需要做成可配置的

ZGC 引數:

ZGC 不用調優,是自適應的

  1. -XX:+UseZGC:使用 ZGC

安全點控制

關於安全點,可以檢視這篇文章:https://zhuanlan.zhihu.com/p/161710652

  1. -XX:-UseBiasedLocking:禁用偏向鎖,偏向鎖其實未來會被完全移除(參考:),目前我們們都是高併發的環境,偏向鎖基本沒啥用並且還有負面影響
  2. -XX:GuaranteedSafepointInterval=0:禁用定時安全點任務,沒必要,我們們不是那種熱點程式碼經常改變,資源珍貴的場景,並且如果是 ZGC 本身就會定時進入安全點進行 GC 檢查,更沒必要了
  3. -XX:+UseCountedLoopSafepoints:防止大有界迴圈帶來的遲遲不進入安全點導致 GC STW 時間過長

JFR 配置

JFR 使用請參考:https://zhuanlan.zhihu.com/p/161710652

-XX:StartFlightRecording=disk=true,maxsize=4096m,maxage=3d -XX:FlightRecorderOptions=maxchunksize=128m

模組化限制

--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.base/java.time=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/jdk.internal.access=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED

Java 16 將 --illegal-access 的預設值從 permit 改成了 deny (JEP: https://openjdk.java.net/jeps/396),Java 17 直接移除了這個選項 (JEP: https://openjdk.java.net/jeps/403),所以現在要打破模組化封裝,必須通過這個命令具體打破某些模組向某些模組的暴露。這裡包含了一些常用的可能會被反射訪問的 java.base 下的 package,向所有未命名模組暴露(我們自己的專案一般不會指定模組名,如果你指定了就換成具體你的模組名)

這個也能從下面的報錯中看出:

Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @7586beff

現在啟動引數配置有點複雜,沒法指定某個模組下的所有包都向某個模組暴露,並且未來也沒有這個打算,參考:https://jigsaw-dev.openjdk.java.narkive.com/Zd1RvaeX/add-opens-for-entire-module

微信搜尋“乾貨滿滿張雜湊”關注公眾號,加作者微信,每日一刷,輕鬆提升技術,斬獲各種offer
image
我會經常發一些很好的各種框架的官方社群的新聞視訊資料並加上個人翻譯字幕到如下地址(也包括上面的公眾號),歡迎關注: