經過前幾篇的鋪墊,現在開始正式進入調優篇,也是大火實際用的到的和感興趣的,但是前期的知識積累還是有必要的,所以還對JVM基礎沒什么了解的,建議還是回看主包的前幾篇內容,當然看其他優秀的博主也是可以的。
性能調優三步驟?
- 發現問題——性能監控:我們通過GUI圖形工具或者JVM命令后對程序進行監控,如出現FullGC頻率過高、CPU負載較高、死鎖、OOM內存泄漏/溢出,或者更加簡單的就是程序莫名其妙響應時間過長大白話就是卡頓。
- 排查問題——性能分析:通過對內存的分析、查看GC日志、查看JVM狀態、已經堆棧信息來確定大概的調優方向,分析出那個區域需要調整。
- 解決問題——性能調優:經過監控和分析已經知道了問題的大致方向,然后進行處理,比如增加內存的大小、使用消息隊列緩存、增加機器也就是分布式減輕壓力、優化代碼控制內存的使用。
?這個圖來自于尚硅谷的宋紅康老師的課程,主包也是跟著宋老師學習整理的資料,想學習的更加細節和系統化可以去某站搜索。
jps:列出當前用戶的所有Java進程
jps -lvm
//-q:只顯示進程ID
//-l:顯示完整包名
//-v:顯示JVM參數
//-m:顯示main方法的參數jps -l
22417 jdk.jcmd/sun.tools.jps.Jps //jps也是個java進程
22260 org.jetbrains.jps.cmdline.Launcher //idea編譯進程
2422 //僵尸或者特殊進程不用理會
32988 com.example.seed.SeedApplication//項目進程
89423 com.intellij.idea.Main //idea主進程
這里說一下,其實我們使用的命令全部是在JDK的bin目錄下,主包這個是Mac文件的類型顯示的就是Unix可執行的文件,要是Window就應該是.exe的文件,有興趣的小伙伴可以自己去查看一下。?另外這個腳步使用的文件就在/home/lib/tools.jar中,在jar包的/sun/tools目錄下,也是一個字節碼文件,又興趣的小伙伴可以自己去查看,這邊就不演示了。(如果使用了-XX:-UserPerfData那么jps和jstat都無法探查這個進程,因為關閉了用戶性能數據)
jstat
?- JVM統計監控工具
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
--<option>//操作參數(必填)
--[-t] //JVM啟動到現在的時間
[-h<lines>]//每幾行重新打印表頭
--<vmid> //Java進程Pid
[<interval>//每次打印的間隔時間1000=1秒
[<count>]]//打印次數
選項 | 說明 |
---|---|
-class | 類加載統計 |
-compiler | JIT編譯器統計 |
-gc | GC統計 |
-gccapacity | 各內存區域容量統計 |
-gcutil | GC統計百分比 |
-gccause | GC統計及原因(同-gcutil ,但包含最近一次GC原因) |
-gcnew | 新生代GC統計 |
-gcnewcapacity | 新生代內存容量統計 |
-gcold | 老年代GC統計 |
-gcoldcapacity | 老年代內存容量統計 |
-gcmetacapacity | 元空間內存容量統計 |
-printcompilation | JVM編譯方法統計 |
?圖太小的話就放大來看吧,這里就是完整的命令格式,當然必須要大就是操作和PID參數其他的看各自的需求了,這個命令也是查看JVM最多的命令,可以查的消息還是很全面的。
列名 | 說明 | 示例值 |
---|---|---|
Timestamp | JVM 啟動后的時間戳(秒) | 134663.6 |
S0C | Survivor 0 區容量(KB) | 0.0 |
S1C | Survivor 1 區容量(KB) | 6144.0 |
S0U | Survivor 0 區已使用(KB) | 0.0 |
S1U | Survivor 1 區已使用(KB) | 6144.0 |
EC | Eden 區容量(KB) | 51200.0 |
EU | Eden 區已使用(KB) | 32768.0 |
OC | 老年代容量(KB) | 40960.0 |
OU | 老年代已使用(KB) | 32549.8 |
MC | Metaspace 容量(KB) | 54528.0 |
MU | Metaspace 已使用(KB) | 53845.6 |
CCSC | 壓縮類空間容量(KB) | 7488.0 |
CCSU | 壓縮類空間已使用(KB) | 7206.5 |
YGC | 年輕代 GC 次數 | 10 |
YGCT | 年輕代 GC 總時間(秒) | 0.126 |
FGC | Full GC 次數 | 0 |
FGCT | Full GC 總時間(秒) | 0.000 |
CGC | Concurrent GC 次數 | 4 |
CGCT | Concurrent GC 總時間(秒) | 0.002 |
GCT | 所有 GC 總時間(秒) | 0.128 |
這個呢就是各個打印代表的意思了。其他的操作主包我就不演示了。其實使用這個命令就可以判斷內存是否有益處的風險,比如我們采用抽樣調查的方法,相同的時間間隔下去對老年區進行監控,如果老年區內存占用在某一個時間段激增或者每段時間都增加的比較多,可能就是內存泄漏無法回收了。因為一般情況下除了靜態文件和Spring容器、數據庫連接池、監聽器等不會被回收,其他的基本上這個請求已結束等到下一個E去GC就會被回收的,如果一直沒有被回收肯定就是內存泄漏了。
jinfo
?:查看和修改正在運行的 Java 進程的配置參數
jinfo [option] <pid>
參數選項 | 作用 |
---|---|
-flags | 顯示所有 JVM 參數(包括默認參數) |
-sysprops | 顯示所有系統屬性(相當于 System.getProperties()) |
-flag <name> | 顯示指定 JVM 參數的值 |
-flag [+/-]<name> | 啟用或禁用指定的布爾型 JVM 參數 |
-flag <name>=<value> | 設置指定的 JVM 參數值 |
無參數 | 同時顯示系統屬性和 JVM 參數 |
//查看最大的堆空間值jinfo -flag MaxHeapSize 32988
-XX:MaxHeapSize=4294967296-flag [+/-]<name> - 修改布爾型參數
# 啟用PrintGCDetails
jinfo -flag +PrintGCDetails 12345-flag <name>=<value> - 修改數值型參數
# 修改MaxHeapFreeRatio
jinfo -flag MaxHeapFreeRatio=70 12345
好了主包這里只示范簡單的幾個命令,主包個人其實這個命令只有兩個操作,一個是flags一個就是flag,也很好記憶了,而且用處也是非常大的,就比如這個動態修改JVM參數了。不加任何參數查看的信息就非常的全了,大火快去自行嘗試一下。另外不是所有的JVM參數都能被修改的,這也是人之常情了,只有被manageable標記的參數。java -XX:+PrintFlagsFinal | grep manageable這個命令可以查看,大概能改的參數就是這些了,主包這個是JDK21可能和大家的不一樣,大家還是自己看看吧。
?jmap
?:用于生成Java堆轉儲快照(heap dump)和分析堆內存使用情況
這個命令的作用主要就是生成dump的二進制文件,然后進行內存分析的,當然需要借助工作不然我們可是看不懂二進制文件的,然后就是查詢類加載器信息?、?查看finalizer隊列??、分析對象內存分布?(用的多多主包已經飆紅了)。
jmap [options] <pid> # 連接運行中的Java進程
jmap [options] <executable> <core> # 連接核心轉儲文件
jmap [options] [server_id@]<remote server IP or hostname> # 連接遠程調試服務器
參數 | 功能描述 | 適用場景 |
---|---|---|
-dump | 生成堆轉儲文件(Heap Dump) | 內存泄漏分析、OOM 問題診斷 |
-heap | 顯示堆內存配置和使用摘要 | 快速查看堆內存分配情況 |
-histo | 顯示堆中對象統計直方圖 | 分析對象內存占用 |
-clstats | 顯示類加載器統計信息 | 類加載問題診斷 |
-finalizerinfo | 顯示等待 finalize 的對象 | 對象回收問題診斷 |
-F | 強制模式(當進程掛起時使用) | JVM 無響應時的強制轉儲 |
#活著的對象
jmap -dump:[live,]format=b,file=<filename> <pid>
#全部的對象包括死的
jmap -dump:format=b,file=<filename> <pid>
//例如
jmap -dump:live,format=b,file=./Downloads/testH.hprof 32988
關鍵詞 | 作用 | 是否可選 | 示例值 |
---|---|---|---|
-dump | 主命令,表示要執行堆轉儲操作 | 必選 | - |
live | 只轉儲存活對象(會觸發Full GC) | 可選 | 包含或不包含 |
format=b | 指定輸出格式為二進制 | 必選 | 必須為b |
file=<filename> | 指定輸出文件路徑 | 必選 | heap.hprof |
<pid> | 目標Java進程ID | 必選 | 1234 |
在我們分析的時候大多數選擇的都是存活的對象,因為?一般沒有被回收的對象就是存活的而且要死全部都看的話一個是這個文件回很大,另一個就是分析起來不是那么好分析需要的時間也就是更長了,此外還有一個辦法可以獲得dump文件,那就是JVM參數,因為大多少程序出現OOM崩潰的時候開發人員是不能第一時間獲得這個文件的,來不及和不可預測所有我們需要使用JVM參數來獲取崩潰時的內存快照。
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/to/dump/file.hprof
這倆參數相信大家也能看明白我就不介紹了。下面就是-heap和-histo的運行實例了,其他的就不演示了。
#查看堆內存信息
jmap -heap <pid>
Attaching to process ID 12345, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11using thread-local object allocation.
Parallel GC with 4 thread(s) # 使用的GC類型Heap Configuration: # 堆配置參數MinHeapFreeRatio = 40MaxHeapFreeRatio = 70MaxHeapSize = 2147483648 (2048.0MB) # 最大堆NewSize = 715653120 (682.5MB) # 新生代大小MaxNewSize = 715653120 (682.5MB)OldSize = 1431830528 (1365.5MB) # 老年代大小NewRatio = 2 # 新生代/老年代比例SurvivorRatio = 8 # Eden/Survivor比例MetaspaceSize = 21807104 (20.796875MB) # 元空間CompressedClassSpaceSize = 1073741824 (1024.0MB)G1HeapRegionSize = 0 (0.0MB)Heap Usage: # 堆使用情況
PS Young Generation # 并行年輕代
Eden Space: # Eden區capacity = 537919488 (513.0MB)used = 536870912 (512.0MB)free = 1048576 (1.0MB)99.8% used
From Space: # Survivor From區capacity = 89128960 (85.0MB)used = 0 (0.0MB)free = 89128960 (85.0MB)0.0% used
To Space: # Survivor To區capacity = 89128960 (85.0MB)used = 0 (0.0MB)free = 89128960 (85.0MB)0.0% used
PS Old Generation # 并行老年代capacity = 1431830528 (1365.5MB)used = 536870912 (512.0MB)free = 894959616 (853.5MB)37.5% used# 其他信息...
jmap -histo[:live] <pid> [> 輸出文件]
# 統計所有對象(包含未被回收的對象)前20個
jmap -histo 12345 | head -20jmap -histo:live 32988 | head -20num #instances實例數量 #bytes字節大小 class name (module)
-------------------------------------------------------1: 81074 12879208 [B (java.base@21.0.5)2: 73927 1774248 java.lang.String (java.base@21.0.5)3: 18267 1607496 java.lang.reflect.Method (java.base@21.0.5)4: 48478 1551296 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.5)5: 13055 1546952 java.lang.Class (java.base@21.0.5)6: 7083 1236824 [I (java.base@21.0.5)7: 13298 842456 [Ljava.lang.Object; (java.base@21.0.5)8: 6582 485232 [Ljava.util.HashMap$Node; (java.base@21.0.5)9: 12046 481840 java.util.LinkedHashMap$Entry (java.base@21.0.5)10: 7367 471488 java.util.LinkedHashMap (java.base@21.0.5)11: 393 452112 [Ljava.util.concurrent.ConcurrentHashMap$Node; (java.base@21.0.5)12: 11288 361216 java.util.HashMap$Node (java.base@21.0.5)13: 18351 293616 java.lang.Object (java.base@21.0.5)14: 10053 260904 [Ljava.lang.Class; (java.base@21.0.5)15: 4872 233856 java.lang.invoke.MemberName (java.base@21.0.5)16: 8861 212664 org.springframework.core.MethodClassKey17: 2970 166320 org.springframework.core.ResolvableType18: 353 158216 [Ljdk.internal.vm.FillerElement; (java.base@21.0.5)
總結
本篇主要講了進行性能調優要做的那些步驟,以及jps、jstat、jinfo、jamp的命令介紹,請大家一定要多多嘗試,光看肯定是記不住的。