1. 程式人生 > >【搞定Jvm面試】 JDK監控和故障處理工具揭祕

【搞定Jvm面試】 JDK監控和故障處理工具揭祕

本文已經收錄自筆者開源的 JavaGuide: https://github.com/Snailclimb (【Java學習+面試指南】 一份涵蓋大部分Java程式設計師所需要掌握的核心知識)如果覺得不錯的還,不妨去點個Star,鼓勵一下!

JDK 監控和故障處理工具總結

JDK 命令列工具

這些命令在 JDK 安裝目錄下的 bin 目錄下:

  • jps (JVM Process Status): 類似 UNIX 的 ps 命令。使用者檢視所有 Java 程序的啟動類、傳入引數和 Java 虛擬機器引數等資訊;
  • jstat( JVM Statistics Monitoring Tool): 用於收集 HotSpot 虛擬機器各方面的執行資料;
  • jinfo (Configuration Info for Java) : Configuration Info forJava,顯示虛擬機器配置資訊;
  • jmap (Memory Map for Java) :生成堆轉儲快照;
  • jhat (JVM Heap Dump Browser ) : 用於分析 heapdump 檔案,它會建立一個 HTTP/HTML 伺服器,讓使用者可以在瀏覽器上檢視分析結果;
  • jstack (Stack Trace for Java):生成虛擬機器當前時刻的執行緒快照,執行緒快照就是當前虛擬機器內每一條執行緒正在執行的方法堆疊的集合。

jps:檢視所有 Java 程序

jps(JVM Process Status) 命令類似 UNIX 的 ps 命令。

jps:顯示虛擬機器執行主類名稱以及這些程序的本地虛擬機器唯一 ID(Local Virtual Machine Identifier,LVMID)。jps -q :只輸出程序的本地虛擬機器唯一 ID。

C:\Users\SnailClimb>jps
7360 NettyClient2
17396
7972 Launcher
16504 Jps
17340 NettyServer

jps -l:輸出主類的全名,如果程序執行的是 Jar 包,輸出 Jar 路徑。

C:\Users\SnailClimb>jps -l
7360 firstNettyDemo.NettyClient2
17396
7972 org.jetbrains.jps.cmdline.Launcher
16492 sun.tools.jps.Jps
17340 firstNettyDemo.NettyServer

jps -v:輸出虛擬機器程序啟動時 JVM 引數。

jps -m:輸出傳遞給 Java 程序 main() 函式的引數。

jstat: 監視虛擬機器各種執行狀態資訊

jstat(JVM Statistics Monitoring Tool) 使用於監視虛擬機器各種執行狀態資訊的命令列工具。 它可以顯示本地或者遠端(需要遠端主機提供 RMI 支援)虛擬機器程序中的類資訊、記憶體、垃圾收集、JIT 編譯等執行資料,在沒有 GUI,只提供了純文字控制檯環境的伺服器上,它將是執行期間定位虛擬機器效能問題的首選工具。

jstat 命令使用格式:

jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

比如 jstat -gc -h3 31736 1000 10表示分析程序 id 為 31736 的 gc 情況,每隔 1000ms 列印一次記錄,列印 10 次停止,每 3 行後列印指標頭部。

常見的 option 如下:

  • jstat -class vmid :顯示 ClassLoader 的相關資訊;
  • jstat -compiler vmid :顯示 JIT 編譯的相關資訊;
  • jstat -gc vmid :顯示與 GC 相關的堆資訊;
  • jstat -gccapacity vmid :顯示各個代的容量及使用情況;
  • jstat -gcnew vmid :顯示新生代資訊;
  • jstat -gcnewcapcacity vmid :顯示新生代大小與使用情況;
  • jstat -gcold vmid :顯示老年代和永久代的資訊;
  • jstat -gcoldcapacity vmid :顯示老年代的大小;
  • jstat -gcpermcapacity vmid :顯示永久代大小;
  • jstat -gcutil vmid :顯示垃圾收集資訊;

另外,加上 -t引數可以在輸出資訊上加一個 Timestamp 列,顯示程式的執行時間。

jinfo: 實時地檢視和調整虛擬機器各項引數

jinfo vmid :輸出當前 jvm 程序的全部引數和系統屬性 (第一部分是系統的屬性,第二部分是 JVM 的引數)。

jinfo -flag name vmid :輸出對應名稱的引數的具體值。比如輸出 MaxHeapSize、檢視當前 jvm 程序是否開啟列印 GC 日誌 ( -XX:PrintGCDetails :詳細 GC 日誌模式,這兩個都是預設關閉的)。

C:\Users\SnailClimb>jinfo  -flag MaxHeapSize 17340
-XX:MaxHeapSize=2124414976
C:\Users\SnailClimb>jinfo  -flag PrintGC 17340
-XX:-PrintGC

使用 jinfo 可以在不重啟虛擬機器的情況下,可以動態的修改 jvm 的引數。尤其在線上的環境特別有用,請看下面的例子:

jinfo -flag [+|-]name vmid 開啟或者關閉對應名稱的引數。

C:\Users\SnailClimb>jinfo  -flag  PrintGC 17340
-XX:-PrintGC

C:\Users\SnailClimb>jinfo  -flag  +PrintGC 17340

C:\Users\SnailClimb>jinfo  -flag  PrintGC 17340
-XX:+PrintGC

jmap:生成堆轉儲快照

jmap(Memory Map for Java)命令用於生成堆轉儲快照。 如果不使用 jmap 命令,要想獲取 Java 堆轉儲,可以使用 “-XX:+HeapDumpOnOutOfMemoryError” 引數,可以讓虛擬機器在 OOM 異常出現之後自動生成 dump 檔案,Linux 命令下可以通過 kill -3 傳送程序退出訊號也能拿到 dump 檔案。

jmap 的作用並不僅僅是為了獲取 dump 檔案,它還可以查詢 finalizer 執行佇列、Java 堆和永久代的詳細資訊,如空間使用率、當前使用的是哪種收集器等。和jinfo一樣,jmap有不少功能在 Windows 平臺下也是受限制的。

示例:將指定應用程式的堆快照輸出到桌面。後面,可以通過 jhat、Visual VM 等工具分析該堆檔案。

C:\Users\SnailClimb>jmap -dump:format=b,file=C:\Users\SnailClimb\Desktop\heap.hprof 17340
Dumping heap to C:\Users\SnailClimb\Desktop\heap.hprof ...
Heap dump file created

jhat: 分析 heapdump 檔案

jhat 用於分析 heapdump 檔案,它會建立一個 HTTP/HTML 伺服器,讓使用者可以在瀏覽器上檢視分析結果。

C:\Users\SnailClimb>jhat C:\Users\SnailClimb\Desktop\heap.hprof
Reading from C:\Users\SnailClimb\Desktop\heap.hprof...
Dump file created Sat May 04 12:30:31 CST 2019
Snapshot read, resolving...
Resolving 131419 objects...
Chasing references, expect 26 dots..........................
Eliminating duplicate references..........................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

訪問 http://localhost:7000/

jstack :生成虛擬機器當前時刻的執行緒快照

jstack(Stack Trace for Java)命令用於生成虛擬機器當前時刻的執行緒快照。執行緒快照就是當前虛擬機器內每一條執行緒正在執行的方法堆疊的集合.

生成執行緒快照的目的主要是定位執行緒長時間出現停頓的原因,如執行緒間死鎖、死迴圈、請求外部資源導致的長時間等待等都是導致執行緒長時間停頓的原因。執行緒出現停頓的時候通過jstack來檢視各個執行緒的呼叫堆疊,就可以知道沒有響應的執行緒到底在後臺做些什麼事情,或者在等待些什麼資源。

下面是一個執行緒死鎖的程式碼。我們下面會通過 jstack 命令進行死鎖檢查,輸出死鎖資訊,找到發生死鎖的執行緒。

public class DeadLockDemo {
    private static Object resource1 = new Object();//資源 1
    private static Object resource2 = new Object();//資源 2

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "執行緒 1").start();

        new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + "get resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                }
            }
        }, "執行緒 2").start();
    }
}

Output

Thread[執行緒 1,5,main]get resource1
Thread[執行緒 2,5,main]get resource2
Thread[執行緒 1,5,main]waiting get resource2
Thread[執行緒 2,5,main]waiting get resource1

執行緒 A 通過 synchronized (resource1) 獲得 resource1 的監視器鎖,然後通過Thread.sleep(1000);讓執行緒 A 休眠 1s 為的是讓執行緒 B 得到執行然後獲取到 resource2 的監視器鎖。執行緒 A 和執行緒 B 休眠結束了都開始企圖請求獲取對方的資源,然後這兩個執行緒就會陷入互相等待的狀態,這也就產生了死鎖。

通過 jstack 命令分析:

C:\Users\SnailClimb>jps
13792 KotlinCompileDaemon
7360 NettyClient2
17396
7972 Launcher
8932 Launcher
9256 DeadLockDemo
10764 Jps
17340 NettyServer

C:\Users\SnailClimb>jstack 9256

輸出的部分內容如下:

Found one Java-level deadlock:
=============================
"執行緒 2":
  waiting to lock monitor 0x000000000333e668 (object 0x00000000d5efe1c0, a java.lang.Object),
  which is held by "執行緒 1"
"執行緒 1":
  waiting to lock monitor 0x000000000333be88 (object 0x00000000d5efe1d0, a java.lang.Object),
  which is held by "執行緒 2"

Java stack information for the threads listed above:
===================================================
"執行緒 2":
        at DeadLockDemo.lambda$main$1(DeadLockDemo.java:31)
        - waiting to lock <0x00000000d5efe1c0> (a java.lang.Object)
        - locked <0x00000000d5efe1d0> (a java.lang.Object)
        at DeadLockDemo$$Lambda$2/1078694789.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"執行緒 1":
        at DeadLockDemo.lambda$main$0(DeadLockDemo.java:16)
        - waiting to lock <0x00000000d5efe1d0> (a java.lang.Object)
        - locked <0x00000000d5efe1c0> (a java.lang.Object)
        at DeadLockDemo$$Lambda$1/1324119927.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

可以看到 jstack 命令已經幫我們找到發生死鎖的執行緒的具體資訊。

JDK 視覺化分析工具

JConsole:Java 監視與管理控制檯

JConsole 是基於 JMX 的視覺化監視、管理工具。可以很方便的監視本地及遠端伺服器的 java 程序的記憶體使用情況。你可以在控制檯輸出console命令啟動或者在 JDK 目錄下的 bin 目錄找到jconsole.exe然後雙擊啟動。

連線 Jconsole

如果需要使用 JConsole 連線遠端程序,可以在遠端 Java 程式啟動時加上下面這些引數:

-Djava.rmi.server.hostname=外網訪問 ip 地址 
-Dcom.sun.management.jmxremote.port=60001   //監控的埠號
-Dcom.sun.management.jmxremote.authenticate=false   //關閉認證
-Dcom.sun.management.jmxremote.ssl=false

在使用 JConsole 連線時,遠端程序地址如下:

外網訪問 ip 地址:60001 

檢視 Java 程式概況

記憶體監控

JConsole 可以顯示當前記憶體的詳細資訊。不僅包括堆記憶體/非堆記憶體的整體資訊,還可以細化到 eden 區、survivor 區等的使用情況,如下圖所示。

點選右邊的“執行 GC(G)”按鈕可以強制應用程式執行一個 Full GC。

  • 新生代 GC(Minor GC):指發生新生代的的垃圾收集動作,Minor GC 非常頻繁,回收速度一般也比較快。
  • 老年代 GC(Major GC/Full GC):指發生在老年代的 GC,出現了 Major GC 經常會伴隨至少一次的 Minor GC(並非絕對),Major GC 的速度一般會比 Minor GC 的慢 10 倍以上。

執行緒監控

類似我們前面講的 jstack 命令,不過這個是視覺化的。

最下面有一個"檢測死鎖 (D)"按鈕,點選這個按鈕可以自動為你找到發生死鎖的執行緒以及它們的詳細資訊 。

Visual VM:多合一故障處理工具

VisualVM 提供在 Java 虛擬機器 (Java Virutal Machine, JVM) 上執行的 Java 應用程式的詳細資訊。在 VisualVM 的圖形使用者介面中,您可以方便、快捷地檢視多個 Java 應用程式的相關資訊。Visual VM 官網:https://visualvm.github.io/ 。Visual VM 中文文件:https://visualvm.github.io/documentation.html。

下面這段話摘自《深入理解 Java 虛擬機器》。

VisualVM(All-in-One Java Troubleshooting Tool)是到目前為止隨 JDK 釋出的功能最強大的執行監視和故障處理程式,官方在 VisualVM 的軟體說明中寫上了“All-in-One”的描述字樣,預示著他除了執行監視、故障處理外,還提供了很多其他方面的功能,如效能分析(Profiling)。VisualVM 的效能分析功能甚至比起 JProfiler、YourKit 等專業且收費的 Profiling 工具都不會遜色多少,而且 VisualVM 還有一個很大的優點:不需要被監視的程式基於特殊 Agent 執行,因此他對應用程式的實際效能的影響很小,使得他可以直接應用在生產環境中。這個優點是 JProfiler、YourKit 等工具無法與之媲美的。

VisualVM 基於 NetBeans 平臺開發,因此他一開始就具備了外掛擴充套件功能的特性,通過外掛擴充套件支援,VisualVM 可以做到:

  • 顯示虛擬機器程序以及程序的配置、環境資訊(jps、jinfo)。
  • 監視應用程式的 CPU、GC、堆、方法區以及執行緒的資訊(jstat、jstack)。
  • dump 以及分析堆轉儲快照(jmap、jhat)。
  • 方法級的程式執行效能分析,找到被呼叫最多、執行時間最長的方法。
  • 離執行緒序快照:收集程式的執行時配置、執行緒 dump、記憶體 dump 等資訊建立一個快照,可以將快照發送開發者處進行 Bug 反饋。
  • 其他 plugins 的無限的可能性......

這裡就不具體介紹 VisualVM 的使用,如果想了解的話可以看:

  • https://visualvm.github.io/documentation.html
  • https://www.ibm.com/developerworks/cn/java/j-lo-visualvm/index.html