1. 概述
前面幾篇文章分析了 JVM 的一些概念,大部分都是偏理論的,本文介紹一些可以實操的 JVM 性能監控與分析工具。
主要包括 JDK 自帶的一些常用工具,以及阿里開源的 Java 診斷工具 Arthas。
2. 性能監控與故障處理工具
2.1 JDK 自帶工具
JDK 自帶的幾個常用工具如下:
名稱 | 主要作用 |
---|---|
jps | JVM Process Status Tool, 顯示指定系統內所有的 HotSpot 虛擬機進程 |
jstat | JVM Statistics Monitoring Tool, 用於收集 HotSpot 虛擬機各方面的運行數據 |
jinfo | Configuration Info for Java, 顯示虛擬機配置信息 |
jmap | Memory Map for Java, 生成虛擬機的內存轉儲快照(heapdump 文件) |
jhat | JVM Heap Analysis Tool, 用於分析 heapdump 文件(它會建立一個 HTTP/HTML 服務器,讓用戶可以在瀏覽器上查看分析結果) |
jstack | Stack Trace for Java, 顯示虛擬機的線程快照 |
這裏只是少部分,其他更多命令可以參考官方文檔:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/toc.html
2.1.1 jps: 虛擬機進程狀況工具
-
命令格式
jps [ options ] [ hostid ]
-
jps
$ jps
15236 Jps
14966 Example1
-
jps -l
$ jps -l
15249 sun.tools.jps.Jps
14966 com.jaxer.jvm.egs.Example1
-
jps -m
$ jps -m
15264 Jps -m
14966 Example1
-
jps -v
$ jps -v
14966 Example1 -Dvisualvm.id=44321340563858 -Xmx50m -Xms50m -XX:+PrintGCDetails -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=61849:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
15278 Jps -Dapplication.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home -Xms8m
-
jps -q
$ jps -q
9938
14966
15334
2.1.2 jstat: 虛擬機統計信息監視工具
-
命令格式
jstat [option vmid [interval[s|ms] [count]] ]
-
示例 1:監控堆內存信息
$ jstat -gc 14966
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
2048.0 2048.0 0.0 0.0 12800.0 9345.8 34304.0 26638.8 5248.0 4971.3 640.0 554.9 2 0.032 2 0.049 0.082
如圖所示:
參數主要是新生代、老年代的內存空間佔用情況以及 GC 的次數和時間,說明如下:
S0: Survivor space 0 utilization as a percentage of the space's current capacity.
S1: Survivor space 1 utilization as a percentage of the space's current capacity.
E: Eden space utilization as a percentage of the space's current capacity.
O: Old space utilization as a percentage of the space's current capacity.
M: Metaspace utilization as a percentage of the space's current capacity.
CCS: Compressed class space utilization as a percentage.
YGC: Number of young generation GC events.
YGCT: Young generation garbage collection time.
FGC: Number of full GC events.
FGCT: Full garbage collection time.
GCT: Total garbage collection time.
官方文檔:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html#BEHHGFAE
-
示例 2:每隔 1000 毫秒打印堆內存信息,打印 10 次
$ jstat -gc 14966 1000 10
如圖所示:
-
示例 3:查看類加載/卸載信息
$ jstat -class 14966
Loaded Bytes Unloaded Bytes Time
829 1604.4 0 0.0 0.37
2.1.3 jinfo: Java 配置信息工具
-
查看 JVM 啓動參數
$ jinfo -flags 26472
VM Flags:
-XX:CICompilerCount=3 -XX:InitialHeapSize=52428800 -XX:MaxHeapSize=52428800 -XX:MaxNewSize=17301504 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=17301504 -XX:OldSize=35127296 -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
值得注意的是,JDK 8 使用該命令時會拋出如下異常:
$ jinfo -flags 26284
Attaching to process ID 26284, please wait...
Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can't attach symbolicator to the process
sun.jvm.hotspot.debugger.DebuggerException: sun.jvm.hotspot.debugger.DebuggerException: Can't attach symbolicator to the process
at sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal$BsdDebuggerLocalWorkerThread.execute(BsdDebuggerLocal.java:169)
at sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal.attach(BsdDebuggerLocal.java:287)
at sun.jvm.hotspot.HotSpotAgent.attachDebugger(HotSpotAgent.java:671)
at sun.jvm.hotspot.HotSpotAgent.setupDebuggerDarwin(HotSpotAgent.java:659)
at sun.jvm.hotspot.HotSpotAgent.setupDebugger(HotSpotAgent.java:341)
at sun.jvm.hotspot.HotSpotAgent.go(HotSpotAgent.java:304)
at sun.jvm.hotspot.HotSpotAgent.attach(HotSpotAgent.java:140)
at sun.jvm.hotspot.tools.Tool.start(Tool.java:185)
at sun.jvm.hotspot.tools.Tool.execute(Tool.java:118)
at sun.jvm.hotspot.tools.JInfo.main(JInfo.java:138)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at sun.tools.jinfo.JInfo.runTool(JInfo.java:108)
at sun.tools.jinfo.JInfo.main(JInfo.java:76)
Caused by: sun.jvm.hotspot.debugger.DebuggerException: Can't attach symbolicator to the process
at sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal.attach0(Native Method)
at sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal.access$100(BsdDebuggerLocal.java:65)
at sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal$1AttachTask.doit(BsdDebuggerLocal.java:278)
at sun.jvm.hotspot.debugger.bsd.BsdDebuggerLocal$BsdDebuggerLocalWorkerThread.run(BsdDebuggerLocal.java:144)
查資料說是 JVM 的 bug(鏈接:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8160376),在 JDK 9 b129 修復了。
因此,這裏測試該命令使用了 JDK 11,版本信息如下:
$ java -version
java version "11.0.5" 2019-10-15 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.5+10-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.5+10-LTS, mixed mode)
2.1.4 jstack: Java 堆棧跟蹤工具
-
jstack (Stack Trace for Java) 命令用於生成虛擬機當前時刻的線程快照(一般稱爲 threaddump 或 javacore 文件)。
-
線程快照就是當前虛擬機內每一條線程正在執行的方法堆棧的集合,生成堆棧快照的主要目的是定位線程出現長時間停頓的原因,如線程死鎖、死循環、請求外部資源導致的長時間等待等都是導致線程長時間停頓的常見原因。
$ jstack -l 26472 | more
如圖所示:
2.1.5 jmap: Java 內存映像工具
-
查看對象信息
可以使用 jmap 查看內存中的對象數量及內存空間佔用:
jmap [option] <pid>
例如:
-
導出堆轉儲快照
$ jmap -dump:live,format=b,file=/Users/jaxer/Desktop/dump.hprof 26472
Dumping heap to /Users/jaxer/Desktop/dump.hprof ...
Heap dump file created
可以在對應路徑下看到堆轉儲快照文件 dump.hprof。導出來之後,就可以用其它工具分析快照文件了。
2.1.6 jhat: 堆轉儲快照分析工具
分析 jmap 生成的快照文件,命令如下:
$ jhat dump.hprof
Reading from dump.hprof...
Dump file created Sun May 03 17:09:07 CST 2020
Snapshot read, resolving...
Resolving 42293 objects...
Chasing references, expect 8 dots........
Eliminating duplicate references........
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
Server 啓動後,在瀏覽器打開 http://localhost:7000/,可以看到如下信息:
實際工作中,一般不會直接使用 jhat 命令來分析 dump 文件,主要原因:
一般不會在部署應用程序的服務器上直接分析 dump 文件(分析工作一般比較耗時,而且消耗硬件資源,在其他機器上進行時則沒必要受到命令行工具的限制);
jhat 分析功能相對簡陋,VisualVM 等更工具功能強大。
2.1.7 jvisualvm & VisualVM: 堆轉儲快照分析工具
jvisualvm 也是 JDK 自帶的命令,雖然後面獨立發展了。這兩種方式都可以使用。
VisualVM 鏈接:https://visualvm.github.io/
使用 VisualVM 分析上面 jmap 導出的堆棧轉儲文件,導入後如下:
-
概覽
-
對象信息
-
線程信息
這個工具使用起來比 jhat 舒服多了。
2.1.8 jconsole: JVM 性能監控
使用 jconsole 命令會啓動一個用戶界面,如下:
可以選擇本地或者遠程的 JVM 進程進行連接。
本人測試連接時報錯,描述及解決方案詳見:https://blog.csdn.net/u010551118/article/details/105907403
連接成功之後如下:
-
概覽
-
死鎖檢測
除了上面 JDK 自帶的工具,還有個很好用的阿里開源的 Arthas。
2.2 Arthas
官方文檔:
中:https://alibaba.github.io/arthas/
英:https://alibaba.github.io/arthas/en/
2.2.0 準備代碼
爲了演示部分功能,這裏準備了一些簡單的示例代碼:
-
Hello.java
package com.jaxer.jvm.arthas;
import java.time.LocalDateTime;
public class Hello {
public void sayHello() {
System.out.println(LocalDateTime.now() + " hello");
}
}
-
ArthasTest.java
package com.jaxer.jvm.arthas;
import java.util.concurrent.TimeUnit;
public class ArthasTest {
public static void main(String[] args) throws InterruptedException {
while (true) {
TimeUnit.SECONDS.sleep(5);
new Hello().sayHello();
}
}
}
代碼比較簡單,這裏不再贅述。
2.2.1 啓動 Arthas
Arthas 其實是一個 jar 包,下載後運行:
$ java -jar arthas-boot.jar
啓動成功後:
Arthas 會檢測本地 JVM 進程並列出來(參見上面的 jps 命令),選擇前面的序號就能附着到對應的進程。這裏選擇 1,然後回車:
這樣就成功附着到了該進程。接下來就可以執行各種命令來分析 JVM 了。
2.2.2 help
使用 help 命令可以看到 Arthas 的命令概覽:
[arthas@36934]$ help
NAME DESCRIPTION
help Display Arthas Help
keymap Display all the available keymap for the specified connection.
sc Search all the classes loaded by JVM
sm Search the method of classes loaded by JVM
classloader Show classloader info
jad Decompile class
getstatic Show the static field of a class
monitor Monitor method execution statistics, e.g. total/success/failure count, average rt, fail rate, etc.
stack Display the stack trace for the specified class and method
thread Display thread info, thread stack
trace Trace the execution time of specified method invocation.
watch Display the input/output parameter, return object, and thrown exception of specified method invocation
tt Time Tunnel
jvm Display the target JVM information
perfcounter Display the perf counter infornation.
ognl Execute ognl expression.
mc Memory compiler, compiles java files into bytecode and class files in memory.
redefine Redefine classes. @see Instrumentation#redefineClasses(ClassDefinition...)
dashboard Overview of target jvm's thread, memory, gc, vm, tomcat info.
dump Dump class byte array from JVM
heapdump Heap dump
options View and change various Arthas options
cls Clear the screen
reset Reset all the enhanced classes
version Display Arthas version
session Display current session information
sysprop Display, and change the system properties.
sysenv Display the system env.
vmoption Display, and update the vm diagnostic options.
logger Print logger info, and update the logger level
history Display command history
cat Concatenate and print files
echo write arguments to the standard output
pwd Return working directory name
mbean Display the mbean information
grep grep command for pipes.
tee tee command for pipes.
profiler Async Profiler. https://github.com/jvm-profiling-tools/async-profiler
stop Stop/Shutdown Arthas server and exit the console.
此外,還可以查看每個命令的用法舉例,命令後面加 -help,例如:
[arthas@36934]$ help -help
USAGE:
help [-h] [cmd]
SUMMARY:
Display Arthas Help
Examples:
help
help sc
help sm
help watch
OPTIONS:
-h, --help this help
<cmd> command name
2.2.3 dashboard
dashboard 命令可以總覽 JVM 狀況(默認 5 秒刷新一次):
2.2.4 jvm
jvm 可以查看當前 JVM 的運行時信息,比如機器信息、JVM 版本、啓動參數、ClassPath 等:
還有類加載信息、編譯信息、垃圾收集器、內存相關信息:
以及操作系統信息、死鎖等:
2.2.5 thread
線程信息、線程堆棧:
2.2.6 sc & sm
sc 可以查看類加載信息:
[arthas@36934]$ sc com.jaxer.*
com.jaxer.jvm.arthas.ArthasTest
com.jaxer.jvm.arthas.Hello
Affect(row-cnt:2) cost in 36 ms.
sm 可以查看類的方法信息:
[arthas@36934]$ sm com.jaxer.jvm.arthas.ArthasTest
com.jaxer.jvm.arthas.ArthasTest <init>()V
com.jaxer.jvm.arthas.ArthasTest main([Ljava/lang/String;)V
Affect(row-cnt:2) cost in 14 ms.
2.2.7 jad
jad 可以對類進行反編譯,例如反編譯上面的 Hello 類,結果如下:
[arthas@36934]$ jad com.jaxer.jvm.arthas.Hello
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@18b4aac2
+-sun.misc.Launcher$ExtClassLoader@14612fee
Location:
/Users/jaxer/GitHub-JiaoXR/Java-Learning/jvm-learning/target/classes/
/*
* Decompiled with CFR.
*/
package com.jaxer.jvm.arthas;
import java.time.LocalDateTime;
public class Hello {
public void sayHello() {
System.out.println(LocalDateTime.now() + " hello");
}
}
Affect(row-cnt:1) cost in 260 ms.
2.2.8 redefine
redefine 就是熱部署,通俗來講就是「開着飛機換引擎」。比如上述代碼的運行結果是每隔 5 秒打印一行代碼,如下:
2020-05-04T16:07:04.242 hello
2020-05-04T16:07:09.247 hello
...
在不停止該程序的情況下,可以改變輸出內容嗎?答案是肯定的!
怎麼做呢?
首先在本地新建一個與 Hello.java 內容完全一樣的文件(名字也完全一樣),然後修改方法內容,新的 Hello.java 文件內容如下(修改了方法輸出內容):
package com.jaxer.jvm.arthas;
import java.time.LocalDateTime;
public class Hello {
public void sayHello() {
System.out.println(LocalDateTime.now() + " I'm hungry");
}
}
然後在本地執行 javac 將其編譯爲 class 文件(注意該文件的路徑爲 /Users/jaxer/Desktop/Hello.class),然後運行 redefine 命令:
[arthas@36934]$ redefine /Users/jaxer/Desktop/Hello.class
redefine success, size: 1
這時候去觀察輸出結果,神奇的事情發生了:
注意:直到現在,原先的程序還是一直運行的,並沒有停下來。
有木有開着飛機換引擎的感覺?nubility!
其他還有很多命令,小夥伴們可以自行嘗試。不知道怎麼用?別忘了 -help。
3. 小結
本文主要介紹了 JDK 性能分析與監測的一些工具,主要包括 JDK 自帶的 jps、jinfo、jstack、jmap 等,以及很好用的阿里開源工具 Arthas。
對於這種實操類的東西,當然還是要多動手練一練咯!
本文內容就到這裏,希望對大家有所幫助~