JVM筆記-性能監控與分析工具

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 文件,主要原因:

  1. 一般不會在部署應用程序的服務器上直接分析 dump 文件(分析工作一般比較耗時,而且消耗硬件資源,在其他機器上進行時則沒必要受到命令行工具的限制);

  2. 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。

對於這種實操類的東西,當然還是要多動手練一練咯!


本文內容就到這裏,希望對大家有所幫助~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章