18.1. 概述
性能診斷是軟件工程師在日常工作中需要經常面對和解決的問題,在用戶體驗至上的今天,解決好應用的性能問題能帶來非常大的收益。
Java 作為最流行的編程語言之一,其應用性能診斷一直受到業界廣泛關注。可能造成 Java 應用出現性能問題的因素非常多,例如線程控制、磁盤讀寫、數據庫訪問、網絡I/O、垃圾收集等。想要定位這些問題,一款優秀的性能診斷工具必不可少。
體會1:使用數據說明問題,使用知識分析問題,使用工具處理問題。
體會2:無監控、不調優!
簡單命令行工具
在我們剛接觸java學習的時候,大家肯定最先了解的兩個命令就是javac,java,那么除此之外,還有沒有其他的命令可以供我們使用呢?
我們進入到安裝jdk的bin目錄,發現還有一系列輔助工具。這些輔助工具用來獲取目標 JVM 不同方面、不同層次的信息,幫助開發人員很好地解決Java應用程序的一些疑難雜癥。
官方源碼地址:http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/jdk.jcmd/share/classes/sun/tools
18.2. jps:查看正在運行的Java進程
jps(Java Process Status):顯示指定系統內所有的HotSpot虛擬機進程(查看虛擬機進程信息),可用于查詢正在運行的虛擬機進程。
說明:對于本地虛擬機進程來說,進程的本地虛擬機ID與操作系統的進程ID是一致的,是唯一的。
基本使用語法為:jps [options] [hostid]
我們還可以通過追加參數,來打印額外的信息。
options參數
- -q:僅僅顯示LVMID(local virtual machine id),即本地虛擬機唯一id。不顯示主類的名稱等
- -l:輸出應用程序主類的全類名 或 如果進程執行的是jar包,則輸出jar完整路徑
- -m:輸出虛擬機進程啟動時傳遞給主類main()的參數
- -v:列出虛擬機進程啟動時的JVM參數。比如:-Xms20m -Xmx50m是啟動程序指定的jvm參數。
說明:以上參數可以綜合使用。
補充:如果某 Java 進程關閉了默認開啟的UsePerfData參數(即使用參數-XX:-UsePerfData),那么jps命令(以及下面介紹的jstat)將無法探知該Java 進程。
hostid參數
RMI注冊表中注冊的主機名。如果想要遠程監控主機上的 java 程序,需要安裝 jstatd。
對于具有更嚴格的安全實踐的網絡場所而言,可能使用一個自定義的策略文件來顯示對特定的可信主機或網絡的訪問,盡管這種技術容易受到IP地址欺詐攻擊。
如果安全問題無法使用一個定制的策略文件來處理,那么最安全的操作是不運行jstatd服務器,而是在本地使用jstat和jps工具。
18.3. jstat:查看JVM統計信息
jstat(JVM Statistics Monitoring Tool):用于監視虛擬機各種運行狀態信息的命令行工具。它可以顯示本地或者遠程虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。在沒有GUI圖形界面,只提供了純文本控制臺環境的服務器上,它將是運行期定位虛擬機性能問題的首選工具。常用于檢測垃圾回收問題以及內存泄漏問題。
官方文檔:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html
基本使用語法為:jstat - [-t] [-h] [ []]
查看命令相關參數:jstat-h 或 jstat-help
其中vmid是進程id號,也就是jps之后看到的前面的號碼,如下:
option參數
選項option可以由以下值構成。
類裝載相關的:
- -class:顯示ClassLoader的相關信息:類的裝載、卸載數量、總空間、類裝載所消耗的時間等
垃圾回收相關的:
- -gc:顯示與GC相關的堆信息。包括Eden區、兩個Survivor區、老年代、永久代等的容量、已用空間、GC時間合計等信息。
- -gccapacity:顯示內容與-gc基本相同,但輸出主要關注Java堆各個區域使用到的最大、最小空間。
- -gcutil:顯示內容與-gc基本相同,但輸出主要關注已使用空間占總空間的百分比。
- -gccause:與-gcutil功能一樣,但是會額外輸出導致最后一次或當前正在發生的GC產生的原因。
- -gcnew:顯示新生代GC狀況
- -gcnewcapacity:顯示內容與-gcnew基本相同,輸出主要關注使用到的最大、最小空間
- -gcold:顯示老年代GC狀況
- -gcoldcapacity:顯示內容與-gcold基本相同,輸出主要關注使用到的最大、最小空間
- -gcpermcapacity:顯示永久代使用到的最大、最小空間。
JIT相關的:
- -compiler:顯示JIT編譯器編譯過的方法、耗時等信息
- -printcompilation:輸出已經被JIT編譯的方法
jstat -class
jstat -compiler
jstat -printcompilation
jstat -gc
jstat -gccapacity
jstat -gcutil
jstat -gccause
jstat -gcnew
jstat -gcnewcapacity
jstat -gcold
jstat -gcoldcapacity
jstat -t
jstat -t -h
表頭 | 含義(字節) |
---|---|
EC | Eden區的大小 |
EU | Eden區已使用的大小 |
S0C | 幸存者0區的大小 |
S1C | 幸存者1區的大小 |
S0U | 幸存者0區已使用的大小 |
S1U | 幸存者1區已使用的大小 |
MC | 元空間的大小 |
MU | 元空間已使用的大小 |
OC | 老年代的大小 |
OU | 老年代已使用的大小 |
CCSC | 壓縮類空間的大小 |
CCSU | 壓縮類空間已使用的大小 |
YGC | 從應用程序啟動到采樣時young gc的次數 |
YGCT | 從應用程序啟動到采樣時young gc消耗時間(秒) |
FGC | 從應用程序啟動到采樣時full gc的次數 |
FGCT | 從應用程序啟動到采樣時的full gc的消耗時間(秒) |
GCT | 從應用程序啟動到采樣時gc的總時間 |
interval參數: 用于指定輸出統計數據的周期,單位為毫秒。即:查詢間隔
count參數: 用于指定查詢的總次數
-t參數: 可以在輸出信息前加上一個Timestamp列,顯示程序的運行時間。單位:秒
-h參數: 可以在周期性數據輸出時,輸出多少行數據后輸出一個表頭信息
補充: jstat還可以用來判斷是否出現內存泄漏。
第1步:在長時間運行的 Java 程序中,我們可以運行jstat命令連續獲取多行性能數據,并取這幾行數據中 OU 列(即已占用的老年代內存)的最小值。
第2步:然后,我們每隔一段較長的時間重復一次上述操作,來獲得多組 OU 最小值。如果這些值呈上漲趨勢,則說明該 Java 程序的老年代內存已使用量在不斷上漲,這意味著無法回收的對象在不斷增加,因此很有可能存在內存泄漏。
18.4. jinfo:實時查看和修改JVM配置參數
jinfo(Configuration Info for Java):查看虛擬機配置參數信息,也可用于調整虛擬機的配置參數。在很多情況卡,Java應用程序不會指定所有的Java虛擬機參數。而此時,開發人員可能不知道某一個具體的Java虛擬機參數的默認值。在這種情況下,可能需要通過查找文檔獲取某個參數的默認值。這個查找過程可能是非常艱難的。但有了jinfo工具,開發人員可以很方便地找到Java虛擬機參數的當前值。
基本使用語法為:jinfo [options] pid
說明:java 進程ID必須要加上
選項 | 選項說明 |
---|---|
no option | 輸出全部的參數和系統屬性 |
-flag name | 輸出對應名稱的參數 |
-flag [±]name | 開啟或者關閉對應名稱的參數 只有被標記為manageable的參數才可以被動態修改 |
-flag name=value | 設定對應名稱的參數 |
-flags | 輸出全部的參數 |
-sysprops | 輸出系統屬性 |
jinfo -sysprops
> jinfo -sysprops
jboss.modules.system.pkgs = com.intellij.rt
java.vendor = Oracle Corporation
sun.java.launcher = SUN_STANDARD
sun.management.compiler = HotSpot 64-Bit Tiered Compilers
catalina.useNaming = true
os.name = Windows 10
...
jinfo -flags
> jinfo -flags 25592
Non-default VM flags: -XX:CICompilerCount=4 -XX:InitialHeapSize=333447168 -XX:MaxHeapSize=5324668928 -XX:MaxNewSize=1774714880 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=111149056 -XX:OldSize=222298112 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line: -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:8040,suspend=y,server=n -Drebel.base=C:\Users\Vector\.jrebel -Drebel.env.ide.plugin.version=2021.1.2 -Drebel.env.ide.version=2020.3.3 -Drebel.env.ide.product=IU -Drebel.env.ide=intellij -Drebel.notification.url=http://localhost:7976 -agentpath:C:\Users\Vector\AppData\Roaming\JetBrains\IntelliJIdea2020.3\plugins\jr-ide-idea\lib\jrebel6\lib\jrebel64.dll -Dmaven.home=D:\eclipse\env\maven -Didea.modules.paths.file=C:\Users\Vector\AppData\Local\JetBrains\IntelliJIdea2020.3\Maven\idea-projects-state-596682c7.properties -Dclassworlds.conf=C:\Users\Vector\AppData\Local\Temp\idea-6755-mvn.conf -Dmaven.ext.class.path=D:\IDEA\plugins\maven\lib\maven-event-listener.jar -javaagent:D:\IDEA\plugins\java\lib\rt\debugger-agent.jar -Dfile.encoding=UTF-8
jinfo -flag
> jinfo -flag UseParallelGC 25592
-XX:+UseParallelGC> jinfo -flag UseG1GC 25592
-XX:-UseG1GC
jinfo -flag name
> jinfo -flag UseParallelGC 25592
-XX:+UseParallelGC> jinfo -flag UseG1GC 25592
-XX:-UseG1GC
jinfo -flag [±]name
> jinfo -flag +PrintGCDetails 25592
> jinfo -flag PrintGCDetails 25592
-XX:+PrintGCDetails> jinfo -flag -PrintGCDetails 25592
> jinfo -flag PrintGCDetails 25592
-XX:-PrintGCDetails
拓展:
- java -XX:+PrintFlagsInitial 查看所有JVM參數啟動的初始值
[Global flags]intx ActiveProcessorCount = -1 {product}uintx AdaptiveSizeDecrementScaleFactor = 4 {product}uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product}uintx AdaptiveSizePausePolicy = 0 {product}
...
- java -XX:+PrintFlagsFinal 查看所有JVM參數的最終值
[Global flags]intx ActiveProcessorCount = -1 {product}
...intx CICompilerCount := 4 {product}uintx InitialHeapSize := 333447168 {product}uintx MaxHeapSize := 1029701632 {product}uintx MaxNewSize := 1774714880 {product}
- java -XX:+PrintCommandLineFlags 查看哪些已經被用戶或者JVM設置過的詳細的XX參數的名稱和值
-XX:InitialHeapSize=332790016 -XX:MaxHeapSize=5324640256 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
18.5. jmap:導出內存映像文件&內存使用情況
jmap(JVM Memory Map):作用一方面是獲取dump文件(堆轉儲快照文件,二進制文件),它還可以獲取目標Java進程的內存相關信息,包括Java堆各區域的使用情況、堆中對象的統計信息、類加載信息等。開發人員可以在控制臺中輸入命令“jmap -help”查閱jmap工具的具體使用方式和一些標準選項配置。
官方幫助文檔:https://docs.oracle.com/en/java/javase/11/tools/jmap.html
基本使用語法為:
- jmap [option]
- jmap [option] <executable
- jmap [option] [server_id@]
選項 | 作用 |
---|---|
-dump | 生成dump文件(Java堆轉儲快照),-dump:live只保存堆中的存活對象 |
-heap | 輸出整個堆空間的詳細信息,包括GC的使用、堆配置信息,以及內存的使用信息等 |
-histo | 輸出堆空間中對象的統計信息,包括類、實例數量和合計容量,-histo:live只統計堆中的存活對象 |
-J | 傳遞參數給jmap啟動的jvm |
-finalizerinfo | 顯示在F-Queue中等待Finalizer線程執行finalize方法的對象,僅linux/solaris平臺有效 |
-permstat | 以ClassLoader為統計口徑輸出永久代的內存狀態信息,僅linux/solaris平臺有效 |
-F | 當虛擬機進程對-dump選項沒有任何響應時,強制執行生成dump文件,僅linux/solaris平臺有效 |
說明:這些參數和linux下輸入顯示的命令多少會有不同,包括也受jdk版本的影響。
> jmap -dump:format=b,file=<filename.hprof> <pid>
> jmap -dump:live,format=b,file=<filename.hprof> <pid>
由于jmap將訪問堆中的所有對象,為了保證在此過程中不被應用線程干擾,jmap需要借助安全點機制,讓所有線程停留在不改變堆中數據的狀態。也就是說,由jmap導出的堆快照必定是安全點位置的。這可能導致基于該堆快照的分析結果存在偏差。
舉個例子,假設在編譯生成的機器碼中,某些對象的生命周期在兩個安全點之間,那么:live選項將無法探知到這些對象。
另外,如果某個線程長時間無法跑到安全點,jmap將一直等下去。與前面講的jstat則不同,垃圾回收器會主動將jstat所需要的摘要數據保存至固定位置之中,而jstat只需直接讀取即可。
18.6.自動生成 Heap Dump 文件
- -XX:+HeapDumpOnOutOfMemoryError
當OutOfMemoryError發生時自動生成 Heap Dump 文件。
當你需要分析Java內存使用情況時,往往是在OOM(OutOfMemoryError)發生時。 - -XX:+HeapDumpBeforeFullGC
當 JVM 執行 FullGC 前執行 dump。 - -XX:+HeapDumpAfterFullGC
當 JVM 執行 FullGC 后執行 dump。 - -XX:+HeapDumpOnCtrlBreak
交互式獲取dump。在控制臺按下快捷鍵Ctrl + Break時,JVM就會轉存一下堆快照。 - -XX:HeapDumpPath=/opt/logs/dumplogs
指定 dump 文件的存儲路徑
18.7. jhat:JDK自帶堆分析工具
jhat(JVM Heap Analysis Tool):Sun JDK提供的jhat命令與jmap命令搭配使用,用于分析jmap生成的heap dump文件(堆轉儲快照)。jhat內置了一個微型的HTTP/HTML服務器,生成dump文件的分析結果后,用戶可以在瀏覽器中查看分析結果(分析虛擬機轉儲快照信息)。
使用了jhat命令,就啟動了一個http服務,端口是7000,即http://localhost:7000/,就可以在瀏覽器里分析。
說明:jhat命令在JDK9、JDK10中已經被刪除,官方建議用VisualVM代替。
基本適用語法:jhat
option參數 | 作用 |
---|---|
-stack false|true | 關閉|打開對象分配調用棧跟蹤 |
-refs false|true | 關閉|打開對象引用跟蹤 |
-port port-number | 設置jhat HTTP Server的端口號,默認7000 |
-exclude exclude-file | 執行對象查詢時需要排除的數據成員 |
-baseline exclude-file | 指定一個基準堆轉儲 |
-debug int | 設置debug級別 |
-version | 啟動后顯示版本信息就退出 |
-J | 傳入啟動參數,比如-J-Xmx512m |
18.8. jstack:打印JVM中線程快照
jstack(JVM Stack Trace):用于生成虛擬機指定進程當前時刻的線程快照(虛擬機堆棧跟蹤)。線程快照就是當前虛擬機內指定進程的每一條線程正在執行的方法堆棧的集合。
生成線程快照的作用:可用于定位線程出現長時間停頓的原因,如線程間死鎖、死循環、請求外部資源導致的長時間等待等問題。這些都是導致線程長時間停頓的常見原因。當線程出現停頓時,就可以用jstack顯示各個線程調用的堆棧情況。
官方幫助文檔:https://docs.oracle.com/en/java/javase/11/tools/jstack.html
在thread dump中,要留意下面幾種狀態
- 死鎖,Deadlock(重點關注)
- 等待資源,Waiting on condition(重點關注)
- 等待獲取監視器,Waiting on monitor entry(重點關注)
- 阻塞,Blocked(重點關注)
- 執行中,Runnable
- 暫停,Suspended
- 對象等待中,Object.wait() 或 TIMED_WAITING
- 停止,Parked
option參數 | 作用 |
---|---|
-F | 當正常輸出的請求不被響應時,強制輸出線程堆棧 |
-l | 除堆棧外,顯示關于鎖的附加信息 |
-m | 如果調用本地方法的話,可以顯示C/C++的堆棧 |
18.9. jcmd:多功能命令行
在JDK 1.7以后,新增了一個命令行工具jcmd。它是一個多功能的工具,可以用來實現前面除了jstat之外所有命令的功能。比如:用它來導出堆、內存使用、查看Java進程、導出線程信息、執行GC、JVM運行時間等。
官方幫助文檔:https://docs.oracle.com/en/java/javase/11/tools/jcmd.html
jcmd擁有jmap的大部分功能,并且在Oracle的官方網站上也推薦使用jcmd命令代jmap命令
**jcmd -l:**列出所有的JVM進程
**jcmd 進程號 help:**針對指定的進程,列出支持的所有具體命令
**jcmd 進程號 具體命令:**顯示指定進程的指令命令的數據
- Thread.print 可以替換 jstack指令
- GC.class_histogram 可以替換 jmap中的-histo操作
- GC.heap_dump 可以替換 jmap中的-dump操作
- GC.run 可以查看GC的執行情況
- VM.uptime 可以查看程序的總執行時間,可以替換jstat指令中的-t操作
- VM.system_properties 可以替換 jinfo -sysprops 進程id
- VM.flags 可以獲取JVM的配置參數信息
18.10. jstatd:遠程主機信息收集
之前的指令只涉及到監控本機的Java應用程序,而在這些工具中,一些監控工具也支持對遠程計算機的監控(如jps、jstat)。為了啟用遠程監控,則需要配合使用jstatd 工具。命令jstatd是一個RMI服務端程序,它的作用相當于代理服務器,建立本地計算機與遠程監控工具的通信。jstatd服務器將本機的Java應用程序信息傳遞到遠程計算機。