JVM调优
JVM調(diào)優(yōu)
什么時候JVM調(diào)優(yōu)
要對Java應(yīng)用程序進行調(diào)優(yōu),優(yōu)化JVM并不是第一選擇。我們首先應(yīng)該考慮軟件架構(gòu)和代碼優(yōu)化等方面,這方面的優(yōu)化可能會取得更大的進步空間。因此假設(shè)我們已經(jīng)對于軟件架構(gòu)、代碼優(yōu)化、數(shù)據(jù)庫優(yōu)化等等做過了一些努力,接著我們希望通過JVM調(diào)優(yōu)來做一些事情,那么我們可以接著往下讀。
性能優(yōu)化的一些方式:
JVM調(diào)優(yōu)指標(biāo)
我們對JVM調(diào)優(yōu)有哪些指標(biāo)呢?一般來說有下面三點:
- 吞吐量(Throughput):is the percentage of time the VM spends executing the application versus time spent performing garbage collection.
- 延時(Latency):is the amount of time required to run a garbage collection event.
- 資源占用(Footprint):is the amount of memory required by the garbage collector to run smoothly.
如果能增加資源投入,提高CPU、內(nèi)存等,自然可以提高吞吐量和減少延時。
對于吞吐量和延時,我們一般通過調(diào)節(jié)垃圾收集參數(shù)來做權(quán)衡。而對于吞吐量和延時的不同的統(tǒng)計方式,可能會得到不同的結(jié)果。
對于垃圾收集對應(yīng)用程序請求的影響的計算方法,可以參考美團文章。通過統(tǒng)計一分鐘內(nèi)請求受影響的占比,來判斷GC影響時間是否減少。
我們還可以開啟GC日志,來看每次垃圾收集的時間、頻率,來判斷GC總時間是否減少。
當(dāng)我們進行各種壓力測試,基準(zhǔn)測試后,拿到這個測試數(shù)據(jù),才能判斷是否達到了我們預(yù)設(shè)的指標(biāo)。
獲取JVM監(jiān)控數(shù)據(jù)
開啟GC log
-XX:+PrintGC
-XX:+PrintGCTimeStamps
-XX:+PrintGCDetails
-Xloggc:<filename>
- -Xloggc specifies where the file is located
- -XX:+PrintGCDetails – includes additional details in the garbage collector log
- -XX:+PrintGCTimeStamps – prints the timestamps to the log
0.134: [GC (Allocation Failure) [PSYoungGen: 65536K->10720K(76288K)] 65536K->40488K(251392K), 0.0190287 secs] [Times: user=0.13 sys=0.04, real=0.02 secs]
0.193: [GC (Allocation Failure) [PSYoungGen: 71912K->10752K(141824K)] 101680K->101012K(316928K), 0.0357512 secs] [Times: user=0.27 sys=0.06, real=0.04 secs]
0.374: [GC (Allocation Failure) [PSYoungGen: 141824K->10752K(141824K)] 232084K->224396K(359424K), 0.0809666 secs] [Times: user=0.58 sys=0.12, real=0.08 secs]
0.455: [Full GC (Ergonomics) [PSYoungGen: 10752K->0K(141824K)] [ParOldGen: 213644K->215361K(459264K)] 224396K->215361K(601088K), [Metaspace: 2649K->2649K(1056768K)], 0.4409247 secs] [Times: user=3.46 sys=0.02, real=0.44 secs]
0.984: [GC (Allocation Failure) [PSYoungGen: 131072K->10752K(190464K)] 346433K->321225K(649728K), 0.1407158 secs] [Times: user=1.28 sys=0.08, real=0.14 secs]
1.168: [GC (System.gc()) [PSYoungGen: 60423K->10752K(190464K)] 370896K->368961K(649728K), 0.0676498 secs] [Times: user=0.53 sys=0.05, real=0.06 secs]
1.235: [Full GC (System.gc()) [PSYoungGen: 10752K->0K(190464K)] [ParOldGen: 358209K->368152K(459264K)] 368961K->368152K(649728K), [Metaspace: 2652K->2652K(1056768K)], 1.1751101 secs] [Times: user=10.64 sys=0.05, real=1.18 secs]
2.612: [Full GC (Ergonomics) [PSYoungGen: 179712K->0K(190464K)] [ParOldGen: 368152K->166769K(477184K)] 547864K->166769K(667648K), [Metaspace: 2659K->2659K(1056768K)], 0.2662589 secs] [Times: user=2.14 sys=0.00, real=0.27 secs]
開啟GClog可得到如上日志,不同的垃圾收集器可能形式略有差異,但都大致相同。上面寫了由于內(nèi)存分配失敗而導(dǎo)致full GC。顯示了新生代,老年代,堆內(nèi)存,元空間垃圾收集前和后的空間大小的變化。垃圾收集時間,用戶態(tài)時間、內(nèi)核態(tài)時間、真正用時等。
關(guān)于gclog 文件的分析,可以參考https://sematext.com/blog/java-garbage-collection-logs/#parallel-and-concurrent-mark-sweep-garbage-collectors。至于好用的免費可視化工具沒有發(fā)現(xiàn),如果有人知道可評論區(qū)指出。
jmap
此命令可以獲得當(dāng)前堆快照,我使用JProfiler來查看堆信息。官方操作文檔
先使用 jps -v查看Java程序進程id,然后使用jmap -dump:live,format=b,file=<filename> <PID>,filename可以起名為xxx.hprof
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heapDump.hprof 此兩個參數(shù)當(dāng)OOM發(fā)生后,會生成堆快照來幫助排查問題。
文件如下:
JProfiler
至于JProfiler的安裝部署不贅述,只列幾張圖片看看大致監(jiān)控的內(nèi)容。
jstat
jstat可實時查看堆狀態(tài)。
先jps -v得到Java程序進程號,再jstat -gcutil <pid> <time interval>(Example: jstat -gcutil 29218 3000 每隔三秒打印一次Java進程號為29218的gc信息)。
S0,S1:幸存者區(qū)
E:Eden區(qū)
O:Old 區(qū)
M:Metaspace
CCS:被編譯的類所占元空間大小
YGC:Young GC 次數(shù)
YGCT:Young GC總時間
FGC,F(xiàn)GCT:Full GC次數(shù),總時間
GCT:GC總時間
關(guān)于jstat -gc 和 jstat -gcutil 區(qū)別,主要是第一個顯示實際大小,比如多少k。第二個顯示百分比
Arthas
使用Arthas也可以監(jiān)控cpu,內(nèi)存,gc等情況,具體可參考官方文檔。也可參考我的這篇文章關(guān)于使用Arthas排查問題
關(guān)于docker中Java應(yīng)用使用Arthas
無論使用什么方式獲得JVM運行信息,最終我們要得到幾組數(shù)據(jù),用數(shù)據(jù)證明我們的調(diào)優(yōu)確實有作用。
關(guān)于垃圾收集器
如果是JDK8,那么會有人說CMS是延時低的,Parallel GC等是吞吐量高的。但實際上還要經(jīng)過測試才能確定。
對于JDK大于8的,比如JDK17等,可以看看G1、ZGC等收集器,測試其是否合適。
GC progress from JDK 8 to JDK 17
JVM OPTs 樣例
-Duser.timezone=Asia/Shanghai
-Xms6G -Xmx6G
-XX:NewSize=3G -XX:MaxNewSize=3G
-XX:SurvivorRatio=10
-XX:MetaspaceSize=2G -XX:MaxMetaspaceSize=2G
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heapDump.hprof
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
Xms Xmx設(shè)置堆大小,兩者一樣可以避免擴容而導(dǎo)致一定延時
SurvivorRatio影響幸存者進入老年代的年齡閾值
MetaspaceSize設(shè)置一樣可以防止擴容而導(dǎo)致延時
HeapDumpOnOutOfMemoryError OOM后輸出堆快照
PrintGCDetails .... 打印GClog
JVM調(diào)優(yōu)案例
例子大部分來自于《深入理解Java虛擬機》,不說具體例子,只說造成結(jié)果
大對象直接進入老年代
導(dǎo)致老年代很快內(nèi)存不夠,導(dǎo)致頻繁full GC,從而更多的延時
內(nèi)存溢出
大量數(shù)據(jù)緩存到Java的堆中得不到釋放,導(dǎo)致OOM。只要我們開啟HeapDumpOnOutOfMemoryError 查看堆信息,基本上就能知道緩存了大量的什么Java對象。
Direct Memory
我們一看到直接內(nèi)存就能想到NIO,可以嘗試擴大Direct Memory
外部命令導(dǎo)致資源占用
Java程序大量調(diào)用外部shell腳本
socket 連接耗盡
發(fā)送的http請求,而響應(yīng)卻很慢才返回,導(dǎo)致socket耗盡
內(nèi)存占用過大
數(shù)據(jù)結(jié)構(gòu)問題,比如我們想查看某個人的一年的出勤率,我們可以看他未出勤的數(shù)據(jù)。比如我們就是要看一個人365天每一天的是否出勤,那么可以用map存365個key、value,但使用一個365長度的01字符串更節(jié)省空間。
safepoint
文中說JVM對for循環(huán)有safepoint,對于for int 的是整個執(zhí)行完才過safepoint,對于for long的是每一個循環(huán)就有safepoint。由于一個for int 執(zhí)行時間過長導(dǎo)致 STW 過長。
詳細(xì)可看:HBase實戰(zhàn):記一次Safepoint導(dǎo)致長時間STW的踩坑之旅
總結(jié)
對于JVM調(diào)優(yōu),我們首先需要知道有什么樣的問題,我們調(diào)優(yōu)的目標(biāo)是什么。一般有三個指標(biāo),吞吐量,延時,資源(footprint)。明確我們需要提高哪項指標(biāo)后,才可進行相應(yīng)的手段進行優(yōu)化。
并且還有一個前提條件,那就是對于系統(tǒng)架構(gòu)和代碼層面的優(yōu)化也做過了,對于數(shù)據(jù)庫相關(guān)的優(yōu)化也做過了,那么我們可以嘗試調(diào)優(yōu)JVM來優(yōu)化相關(guān)指標(biāo)。以為我們不能指望通過調(diào)優(yōu)JVM來大幅提升性能。
僅僅從JVM角度說,如果我們要提高吞吐量,我們可以提高物理機性能,比如多開內(nèi)存。或者換一個更注重吞吐量的垃圾收集器。當(dāng)然也可以調(diào)節(jié)JVM參數(shù)來減少垃圾回收次數(shù)。
比如我們要減少延時,還是多開內(nèi)存。或者換一個更注重降低延時的收集器。當(dāng)然也是可以調(diào)節(jié)JVM參數(shù)減少垃圾回收次數(shù)等等。
如果我們要減少資源,如果可以忍受降低程序性能的話。那么我們能做的可能就是調(diào)節(jié)新生代,老年代比例等,比如我們的應(yīng)用是朝生夕滅多(調(diào)大新生代),還是永久的對象更多(調(diào)大老年代)。
Reference
[深入理解Java虛擬機:JVM高級特性與最佳實踐(第3版)周志明.pdf]
[Guide to the Most Important JVM Parameters]: https://www.baeldung.com/jvm-parameters
[JVM Tuning: How to Prepare Your Environment for Performance Tuning]: https://sematext.com/blog/jvm-performance-tuning/
[從實際案例聊聊Java應(yīng)用的GC優(yōu)化]: https://tech.meituan.com/2017/12/29/jvm-optimize.html
[How to Properly Plan JVM Performance Tuning]: https://www.alibabacloud.com/blog/how-to-properly-plan-jvm-performance-tuning_594663
[Solving java.lang.OutOfMemoryError: Metaspace error]: https://www.mastertheboss.com/java/solving-java-lang-outofmemoryerror-metaspace-error/
[GC progress from JDK 8 to JDK 17]: https://kstefanj.github.io/2021/11/24/gc-progress-8-17.html
[HBase實戰(zhàn):記一次Safepoint導(dǎo)致長時間STW的踩坑之旅]: https://juejin.cn/post/6844903878765314061
總結(jié)
- 上一篇: 日本用微信吗(日本行政区划)
- 下一篇: 饥荒雕像有什么用处