一、概述
??JDK本身提供了許多方便的性能調(diào)優(yōu)及監(jiān)控的小工具,這些小工具雖然沒有說是官方的標(biāo)準(zhǔn)工具,但自從Java誕生這么多年,這些工具一直在被人使用,不得不說這是JDK給開發(fā)者的福利,這些工具包含但不僅限于:jstack、jps、jmap、jConsole、jstat等等。
?由于前些時候用到了jstack這個工具,所以今天來簡單總結(jié)下jstack的使用。
二、jstack使用
首先,jstack會生成JVM當(dāng)前時刻的線程快照,然后我們可以通過它查看某個Java進(jìn)程內(nèi)的線程堆棧信息,通常來說,當(dāng)線上CPU使用率較高的時候,我們可以通過jstack查詢占用CPU較高的一些線程的使用情況,比如發(fā)生了死鎖,線程阻塞等相關(guān)操作。一般情況下,jstack會配合其他命令一塊進(jìn)行操作,比如top,ps等命令。我們先來看下使用jstack命令的一些步驟。
1. 查詢占用CPU最高的進(jìn)程
首先,我們通過top命令查詢當(dāng)前CPU的使用情況,top命令前面已詳細(xì)說過,這里不多說,直接輸入命令:top

這里我們可以看到各個進(jìn)程對CPU的使用占比,并且可以根據(jù)top相關(guān)命令進(jìn)行排序。
2. 查詢該進(jìn)程下占用CPU最高的線程
接下來我們可以根據(jù)進(jìn)程id查詢該進(jìn)程下占用CPU比較高的線程,輸入命令:top -Hp PID,其中PID是上面的那個進(jìn)程id,比如我們這個的:top -Hp 12386:

如果對PS命令比較熟的,還可以直接通過:ps H -eo pid,tid,pcpu | sort -n -k 3 | tail -10 來定位到相關(guān)線程:

如果想看下線程的詳細(xì)信息,還可以通過:cat /proc/進(jìn)程號/task/線程號/status來查看。如果需要查詢該進(jìn)程下所有的線程,我們還可以通過pstree命令,以樹的形式展示:

3. 使用jstack獲取對應(yīng)的線程信息
這里我們獲取到了對應(yīng)的線程id,由于jstack輸出中的線程id是16進(jìn)制的,所以需要先將線程id轉(zhuǎn)為16進(jìn)制:

然后使用jstack命令查看對應(yīng)的堆棧信息:jstack $pid|grep -A N $nid,其中此處的pid指的是進(jìn)程號,而nid指的是該進(jìn)程下占用最多的線程號,比如:jstack 12386|grep -A 30 30c6:

這里來簡單看下各數(shù)據(jù)項的含義:
- 最前面的是線程名稱;當(dāng)我們通過Thread來創(chuàng)建線程的時候,線程會被命名為
Thread-(序號);當(dāng)我們使用連接池通過ThreadFactory來創(chuàng)建線程時,線程會被命名為pool-(序號)-thread-(序號); - tid,指的是線程id;
- prio,指的是線程優(yōu)先級,值越大優(yōu)先級越高;
- os_prio,表示的對應(yīng)操作系統(tǒng)線程的優(yōu)先級,由于并不是所有的操作系統(tǒng)都支持線程優(yōu)先級,所以可能會出現(xiàn)都為0的情況;
- nid,操作系統(tǒng)映射的線程id,每一個java線程都有一個對應(yīng)的操作系統(tǒng)線程;
- java.lang.Thread.State:RUNNABLE,當(dāng)前線程的狀態(tài);如果是WATTING狀態(tài),后面會跟上調(diào)用哪個方法導(dǎo)致的watting狀態(tài);
- 另外線程是否持有鎖信息等,如果持有鎖,則是locked<>;而如果是正在等待獲取鎖,則是 wating for <>;
grep命令中的 -A 表示顯示后面若干行,前面也已經(jīng)學(xué)習(xí)過這個命令,不多說了。本例中可以看到的是Kafka一直在消費(fèi)數(shù)據(jù),正常來說,除了確實(shí)是那種需要密集計算的應(yīng)用之外,一個應(yīng)用的CPU都不會太高,除非出現(xiàn)了一些其他的異常情況。
另外,我們還可以將該進(jìn)程的相關(guān)線程信息導(dǎo)出到stack.dump文件中,然后我們可以在這個文件中查看每個線程的具體狀態(tài):jstack pid(進(jìn)程pid)>stack.dump,這里就不多說了。
三、jstack語法
jstack本身的使用并不復(fù)雜,我們來看下它的一些參數(shù):
deploy@127.0.0.1:~$ jstack -help
Usage:
jstack [-l] <pid>
(to connect to running process)
jstack -F [-m] [-l] <pid>
(to connect to a hung process)
jstack [-m] [-l] <executable> <core>
(to connect to a core file)
jstack [-m] [-l] [server_id@]<remote server IP or hostname>
(to connect to a remote debug server)
Options:
-F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
-m to print both java and native frames (mixed mode)
-l long listing. Prints additional information about locks
-h or -help to print this help message
其中參數(shù)文檔已經(jīng)說的很明確了,這里再說下:
- -l 會打印出額外的鎖信息,在發(fā)生死鎖時可以用jstack -l pid來觀察鎖的持有情況;
- -m 不僅會輸出Java堆棧信息,還會輸出C/C++堆棧信息(比如Native方法);
比如說:
deploy@127.0.0.1:~$ jstack -l 27075 | grep -A 50 6a15
"pool-1-kafka@thread-1" #45 prio=5 os_prio=0 tid=0x00007f63b1f18000 nid=0x6a15 runnable [0x00007f6324c6b000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
...
- locked <0x000000072028f2a8> (a sun.nio.ch.Util$2)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
...
at org.apache.kafka.clients.consumer.KafkaConsumer.pollOnce(KafkaConsumer.java:1096)
at org.apache.kafka.clients.consumer.KafkaConsumer.poll(KafkaConsumer.java:1043)
at ****.KafkaConsumerWorker.run(KafkaConsumerWorker.java:39)
...
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- <0x000000072012cbb0> (a java.util.concurrent.ThreadPoolExecutor$Worker)
"Curator-PathChildrenCache-0" #44 daemon prio=5 os_prio=0 tid=0x00007f63b1ce4800 nid=0x6a14 waiting on condition [0x00007f6324538000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007201df280> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
這里可以看到當(dāng)前線程類型:daemon ,當(dāng)前線程正在等待獲取鎖 0x00000007201df280,并且當(dāng)前線程沒有持有鎖;不過我們更多的是關(guān)注用戶線程。
這里,涉及到了Locked ownable synchronizers,這里其實(shí)有點(diǎn)沒看懂,如果表示是該線程鎖定的資源的話,那上面locked就會展示了;在stackoverflow上看了下,"ownable synchronizers" list只會出現(xiàn)write lock,而不會出現(xiàn)read lock;然后看了下官方文檔:
An ownable synchronizer is a synchronizer that may be exclusively owned by a thread and uses AbstractOwnableSynchronizer (or its subclass) to implement its synchronization property. ReentrantLock and ReentrantReadWriteLock are two examples of ownable synchronizers provided by the platform.
不過還是沒有看太懂,以后看到的時候再補(bǔ)充吧(TODO)。
有關(guān)Locked ownable synchronizers的說明,可以參考:what-is-locked-ownable-synchronizers-in-thread-dump-stackoverflow.com
四、jstack注意事項
在使用jstack之前,我們肯定是需要了解線程的幾種狀態(tài)的,而在這里我們需要關(guān)注的一般有如下幾種:
- RUNNABLE,線程處于執(zhí)行中;一般指該線程正在執(zhí)行狀態(tài)中,該線程占用了資源,正在處理某個請求,或者正在進(jìn)行資源的操作等;
- BLOCKED,線程阻塞;
- WAITING,線程正在等待中;Waiting on condition(等待資源或條件),Waiting for monitor entry(waiting to lock ,等待獲取鎖);
- Deadlock ,死鎖;一個線程鎖了某個資源A,等待另一個資源B;而另一個線程恰好鎖了這個被等待的資源B,在等待資源A;
另外,還需要注意:
- 如果我們要通過dump來查看問題的話,那一次dump可能不足以確認(rèn)問題,可以產(chǎn)生多次 dump信息,如果每次 dump都指向同一個問題,我們基本就可以確定問題的所在。
- 使用jstack的時候,需要對應(yīng)的服務(wù)器權(quán)限,需要注意,如果沒有權(quán)限的話,會提示你不允許的操作。
而有關(guān)jstack更多操作,可以參考以下鏈接:
JVM性能分析工具jstack介紹
JVM故障分析系列之四:jstack生成的Thread Dump日志線程狀態(tài)
jstack線程dump輸出狀態(tài)解釋