Java 線程調優 JDK常用命令行工具 Jstack & Arthas使用筆記 查找耗時線程

1. 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):生成虛擬機當前時刻的線程快照,線程快照就是當前虛擬機內每一條線程正在執行的方法堆棧的集合。

1.1 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() 函數的參數。

1.2 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 列,顯示程序的運行時間。

1.3 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

1.4 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

1.5 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/

1.6 jstack :生成虛擬機當前時刻的線程快照

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

生成線程快照的目的主要是定位線程長時間出現停頓的原因,如線程間死鎖、死循環、請求外部資源導致的長時間等待等都是導致線程長時間停頓的原因。線程出現停頓的時候通過jstack來查看各個線程的調用堆棧,就可以知道沒有響應的線程到底在後臺做些什麼事情,或者在等待些什麼資源。

一般使用查找耗時時間長的線程方法如下

  1. 使用jps查找出java進程的pid,如7777
    或 ps -ef | grep java
  2. 使用top -p 7777觀察進程情況,然後Shift+h,顯示該進程的所有線程。
  3. 找出CPU消耗較多的線程id,如7788,將7788轉換爲16進制0x1e6c,注意是小寫。
  4. 使用jstack 7777 | grep -A 10 0x1e6c 來查詢出具體的線程狀態。
    -A 10表示查找到所在行的後10行

下面是一個線程死鎖的代碼。我們下面會通過 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 命令已經幫我們找到發生死鎖的線程的具體信息。

2. Arthas 使用簡介

當你遇到以下類似問題而束手無策時,Arthas可以幫助你解決:

  1. 這個類從哪個 jar 包加載的?爲什麼會報各種類相關的 Exception?

  2. 我改的代碼爲什麼沒有執行到?難道是我沒 commit?分支搞錯了?

  3. 遇到問題無法在線上 debug,難道只能通過加日誌再重新發布嗎?

  4. 線上遇到某個用戶的數據處理有問題,但線上同樣無法 debug,線下無法重現!

  5. 是否有一個全局視角來查看系統的運行狀況?

  6. 有什麼辦法可以監控到JVM的實時運行狀態?

使用

wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar

常見指令

  1. dashboard

    實時查看應用監控數據

  2. thread

    查看應用程序中所有線程情況

    thread threadId命令查看指定線程狀態信息
    thread -n {n} 打印最忙的n個線程的信息
    trace

  3. trace 類 方法

    查看每個步驟耗時

  4. stack

    stack 類 方法

    輸出當前方法被調用的調用路徑

  5. watch

    方便的觀察到指定方法的調用情況。能觀察到的範圍爲:返回值、拋出異常、入參,通過編寫 OGNL 表達式進行對應變量的查看。

Ref

  1. Arthas 官方文檔 https://alibaba.github.io/arthas/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章