本文的初衷僅供自己做備忘筆記, 內(nèi)容大多從網(wǎng)上搜集和整理, 并非都是自己原創(chuàng).
參考的來源我會在后面注明, 對于可能遺漏的來源, 還請相關原創(chuàng)作者提醒, 非常感謝.
參考來源:
https://www.cnblogs.com/michael-xiang/p/10779566.html
https://www.iteye.com/blog/crane-ding-968862
https://blog.csdn.net/wufaliang003/article/details/80414267
拜讀了三位博主的發(fā)文, 感覺很實用, 故記錄下來.
java自帶的常用工具
JDK本身提供了很多方便的JVM性能調(diào)優(yōu)監(jiān)控工具粹湃,除了集成式的VisualVM和jConsole外, 還有jps、jstack胖喳、jmap仙粱、jhat藕届、jstat勺鸦、hprof等小巧的工具妨退,每一種工具都有其自身的特點讽坏, 用戶可以根據(jù)你需要檢測的應用或者程序片段的狀況锭魔,適當?shù)倪x擇相應的工具進行檢測, 先通過一個表格形式簡要介紹下這幾個命令的作用和使用方法路呜。
命令 | 作用 |
---|---|
jps | 基礎工具 |
jstack | 查看某個Java進程內(nèi)的線程堆棧信息 |
jmap | jmap導出堆內(nèi)存迷捧,然后使用jhat來進行分析 |
jhat | 主要用來解析java堆dump并啟動一個web服務器,然后就可以在瀏覽器中查看堆的dump文件 |
jstat | 主要是對java應用程序的資源和性能進行實時的命令行監(jiān)控胀葱,包括了對heap size和垃圾回收狀況的監(jiān)控 |
hprof | hprof能夠展現(xiàn)CPU使用率漠秋,統(tǒng)計堆內(nèi)存使用情況 |
jps
jps 全稱 JVM Process Status Tool,命令位于 jdk 的 bin 目錄下抵屿,其作用是顯示當前系統(tǒng)的 Java 進程情況膛堤,及其 pid 號。他是 Java自帶的一個命令晌该。
jps 命令用來查看所有 Java 進程肥荔,每一行就是一個 Java 進程信息。
jps 僅查找當前用戶的 Java 進程朝群,而不是當前系統(tǒng)中的所有進程燕耿,要顯示其他用戶的還只能用 ps 命令。
jps 常用參數(shù)
第一列的數(shù)字就是進程的 pid
- jps -l
如果是以 class 方式運行姜胖,會顯示進程的主類 main.class 的全名誉帅,如果是以 jar 包方式運行的,就會輸出 jar 包的完整路徑名
root@VM-1-3-ubuntu:/home/wechatCrawlerJava# jps
5409 Jps
4172 chapter5-0.0.1-SNAPSHOT.jar
root@VM-1-3-ubuntu:/home/wechatCrawlerJava# jps -l
6629 sun.tools.jps.Jps
4172 ./chapter5-0.0.1-SNAPSHOT.jar
- jps -v
輸出傳遞給 JVM 的參數(shù)右莱,v 表示虛擬機蚜锨,jps -vl 比較常見的組合; - jps -V
大寫 v慢蜓,表示通過文件傳遞給 JVM 的參數(shù)
# michael @ Michael-MBP in ~ [16:37:59]
$ jps -v |grep Mybatis
8005 MybatisDemoApplication -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:53364,suspend=y,server=n -XX:TieredStopAtLevel=1 -Xverify:none -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=53363 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:/Users/michael/Library/Caches/IntelliJIdea2018.2/captureAgent/debugger-agent.jar=file:/private/var/folders/m1/ydypchs901lffc5sms07mrp40000gn/T/capture.props -Dfile.encoding=UTF-8
- jps -m
輸出傳遞給 main.class 方法的參數(shù)亚再,實用的一個命令,jps -ml 比較實用的組合晨抡,會顯示包名/類名/參數(shù) - jps -q
只輸出進程的 pid
jstack
簡介
jstack是java虛擬機自帶的一種堆棧跟蹤工具氛悬。
jstack用于生成java虛擬機當前時刻的線程快照则剃。線程快照是當前java虛擬機內(nèi)每一條線程正在執(zhí)行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現(xiàn)長時間停頓的原因如捅,如線程間死鎖棍现、死循環(huán)、請求外部資源導致的長時間等待等镜遣。 線程出現(xiàn)停頓的時候通過jstack來查看各個線程的調(diào)用堆棧己肮,就可以知道沒有響應的線程到底在后臺做什么事情,或者等待什么資源悲关。 如果java程序崩潰生成core文件朴肺,jstack工具可以用來獲得core文件的java stack和native stack的信息,從而可以輕松地知道java程序是如何崩潰和在程序何處發(fā)生問題坚洽。另外戈稿,jstack工具還可以附屬到正在運行的java程序中,看到當時運行的java程序的java stack和native stack的信息, 如果現(xiàn)在運行的java程序呈現(xiàn)hung的狀態(tài)讶舰,jstack是非常有用的鞍盗。
jstack命令主要用來查看Java線程的調(diào)用堆棧的,可以用來分析線程問題(如死鎖)跳昼。
jstack命令
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
-F 當’jstack [-l] pid’沒有相應的時候強制打印棧信息,如果直接jstack無響應時般甲,用于強制jstack,一般情況不需要使用
-l 長列表. 打印關于鎖的附加信息,例如屬于java.util.concurrent的ownable synchronizers列表鹅颊,會使得JVM停頓得長久得多 (可能會差很多倍敷存,比如普通的jstack可能幾毫秒和一次GC沒區(qū)別,加了-l 就是近一秒的時間)堪伍,-l 建議不要用锚烦。一般情況不需要使用
-m 打印java和native c/c++ 框架的所有棧信息.可以打印JVM的堆棧,顯示上Native的棧幀,一般應用排查不需要使用
執(zhí)行命令:
jstack -m 12905
線程狀態(tài)
想要通過jstack命令來分析線程的情況的話帝雇,首先要知道線程都有哪些狀態(tài)涮俄,下面這些狀態(tài)是我們使用jstack命令查看線程堆棧信息時可能會看到的線程的幾種狀態(tài):
- NEW,未啟動的。不會出現(xiàn)在Dump中尸闸。
- RUNNABLE,在虛擬機內(nèi)執(zhí)行的彻亲。
- BLOCKED,受阻塞并等待監(jiān)視器鎖。
- WATING,無限期等待另一個線程執(zhí)行特定操作吮廉。
- TIMED_WATING,有時限的等待另一個線程的特定操作苞尝。
- TERMINATED,已退出的。
Monitor
在多線程的 JAVA程序中宦芦,實現(xiàn)線程之間的同步宙址,就要說說 Monitor。 Monitor是 Java中用以實現(xiàn)線程之間的互斥與協(xié)作的主要手段踪旷,它可以看成是對象或者 Class的鎖曼氛。每一個對象都有,也僅有一個 monitor令野。下 面這個圖舀患,描述了線程和 Monitor之間關系,以 及線程的狀態(tài)轉(zhuǎn)換圖:
- 進入?yún)^(qū)(Entrt Set):表示線程通過synchronized要求獲取對象的鎖气破。如果對象未被鎖住,則迚入擁有者;否則則在進入?yún)^(qū)等待聊浅。一旦對象鎖被其他線程釋放,立即參與競爭。
- 擁有者(The Owner):表示某一線程成功競爭到對象鎖现使。
- 等待區(qū)(Wait Set):表示線程通過對象的wait方法,釋放對象的鎖,并在等待區(qū)等待被喚醒低匙。
從圖中可以看出,一個 Monitor在某個時刻碳锈,只能被一個線程擁有顽冶,該線程就是 “Active Thread”,而其它線程都是 “Waiting Thread”售碳,分別在兩個隊列 “ Entry Set”和 “Wait Set”里面等候强重。在 “Entry Set”中等待的線程狀態(tài)是 “Waiting for monitor entry”,而在 “Wait Set”中等待的線程狀態(tài)是 “in Object.wait()”贸人。 先看 “Entry Set”里面的線程间景。我們稱被 synchronized保護起來的代碼段為臨界區(qū)。當一個線程申請進入臨界區(qū)時艺智,它就進入了 “Entry Set”隊列倘要。對應的 code就像:
synchronized(obj) {
.........
}
調(diào)用修飾
表示線程在方法調(diào)用時,額外的重要的操作。線程Dump分析的重要信息十拣。修飾上方的方法調(diào)用封拧。
- locked <地址> 目標:使用synchronized申請對象鎖成功,監(jiān)視器的擁有者。
- waiting to lock <地址> 目標:使用synchronized申請對象鎖未成功,在迚入?yún)^(qū)等待夭问。
- waiting on <地址> 目標:使用synchronized申請對象鎖成功后,釋放鎖幵在等待區(qū)等待哮缺。
- parking to wait for <地址> 目標
locked
at oracle.jdbc.driver.PhysicalConnection.prepareStatement
- locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
at oracle.jdbc.driver.PhysicalConnection.prepareStatement
- locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
at com.jiuqi.dna.core.internal.db.datasource.PooledConnection.prepareStatement
通過synchronized關鍵字,成功獲取到了對象的鎖,成為監(jiān)視器的擁有者,在臨界區(qū)內(nèi)操作。對象鎖是可以線程重入的甲喝。
waiting to lock
at com.jiuqi.dna.core.impl.CacheHolder.isVisibleIn(CacheHolder.java:165)
- waiting to lock <0x0000000097ba9aa8> (a CacheHolder)
at com.jiuqi.dna.core.impl.CacheGroup$Index.findHolder
at com.jiuqi.dna.core.impl.ContextImpl.find
at com.jiuqi.dna.bap.basedata.common.util.BaseDataCenter.findInfo
通過synchronized關鍵字,沒有獲取到了對象的鎖,線程在監(jiān)視器的進入?yún)^(qū)等待尝苇。在調(diào)用棧頂出現(xiàn),線程狀態(tài)為Blocked。
waiting on
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000da2defb0> (a WorkingThread)
at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo
- locked <0x00000000da2defb0> (a WorkingThread)
at com.jiuqi.dna.core.impl.WorkingThread.run
通過synchronized關鍵字,成功獲取到了對象的鎖后,調(diào)用了wait方法,進入對象的等待區(qū)等待埠胖。在調(diào)用棧頂出現(xiàn),線程狀態(tài)為WAITING或TIMED_WATING糠溜。
parking to wait for
park是基本的線程阻塞原語,不通過監(jiān)視器在對象上阻塞。隨concurrent包會出現(xiàn)的新的機制,不synchronized體系不同直撤。
線程動作
線程狀態(tài)產(chǎn)生的原因
- runnable:狀態(tài)一般為RUNNABLE非竿。
- in Object.wait():等待區(qū)等待,狀態(tài)為WAITING或TIMED_WAITING。
- waiting for monitor entry:進入?yún)^(qū)等待,狀態(tài)為BLOCKED谋竖。
- waiting on condition:等待區(qū)等待红柱、被park承匣。
- sleeping:休眠的線程,調(diào)用了Thread.sleep()。
Wait on condition 該狀態(tài)出現(xiàn)在線程等待某個條件的發(fā)生锤悄。具體是什么原因韧骗,可以結合 stacktrace來分析。 最常見的情況就是線程處于sleep狀態(tài)零聚,等待被喚醒袍暴。 常見的情況還有等待網(wǎng)絡IO:在java引入nio之前,對于每個網(wǎng)絡連接隶症,都有一個對應的線程來處理網(wǎng)絡的讀寫操作政模,即使沒有可讀寫的數(shù)據(jù),線程仍然阻塞在讀寫操作上蚂会,這樣有可能造成資源浪費淋样,而且給操作系統(tǒng)的線程調(diào)度也帶來壓力。在 NewIO里采用了新的機制胁住,編寫的服務器程序的性能和可擴展性都得到提高习蓬。 正等待網(wǎng)絡讀寫,這可能是一個網(wǎng)絡瓶頸的征兆措嵌。因為網(wǎng)絡阻塞導致線程無法執(zhí)行躲叼。一種情況是網(wǎng)絡非常忙,幾 乎消耗了所有的帶寬企巢,仍然有大量數(shù)據(jù)等待網(wǎng)絡讀 寫枫慷;另一種情況也可能是網(wǎng)絡空閑,但由于路由等問題浪规,導致包無法正常的到達或听。所以要結合系統(tǒng)的一些性能觀察工具來綜合分析,比如 netstat統(tǒng)計單位時間的發(fā)送包的數(shù)目笋婿,如果很明顯超過了所在網(wǎng)絡帶寬的限制 ; 觀察 cpu的利用率誉裆,如果系統(tǒng)態(tài)的 CPU時間,相對于用戶態(tài)的 CPU時間比例較高缸濒;如果程序運行在 Solaris 10平臺上足丢,可以用 dtrace工具看系統(tǒng)調(diào)用的情況,如果觀察到 read/write的系統(tǒng)調(diào)用的次數(shù)或者運行時間遙遙領先庇配;這些都指向由于網(wǎng)絡帶寬所限導致的網(wǎng)絡瓶頸斩跌。
死鎖例子
jstack [-l] <pid>, pid可以通過使用jps命令來查看當前Java程序的pid值,-l是可選參數(shù),它可以顯示線程阻塞/死鎖情況。
/**
* 死鎖例子
* @author crane.ding
* @since 2011-3-20
*/
public class DeadLock {
public static void main(String[] args) {
final Object obj_1 = new Object(), obj_2 = new Object();
Thread t1 = new Thread("t1"){
@Override
public void run() {
synchronized (obj_1) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {}
synchronized (obj_2) {
System.out.println("thread t1 done.");
}
}
}
};
Thread t2 = new Thread("t2"){
@Override
public void run() {
synchronized (obj_2) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {}
synchronized (obj_1) {
System.out.println("thread t2 done.");
}
}
}
};
t1.start();
t2.start();
}
}
以上DeadLock類是一個死鎖的例子,假使在我們不知情的情況下,運行DeadLock后,發(fā)現(xiàn)等了N久都沒有在屏幕打印線程完成信息捞慌。這個時候我們就可以使用jps查看該程序的jpid值和使用jstack來生產(chǎn)堆棧結果問題耀鸦。
$ java -cp deadlock.jar DeadLock &
$
$ jps
3076 Jps
448 DeadLock
$ jstack -l 448 > deadlock.jstack
結果文件deadlock.jstack內(nèi)容如下:
2011-03-20 23:05:20
Full thread dump Java HotSpot(TM) Client VM (19.1-b02 mixed mode, sharing):
"DestroyJavaVM" prio=6 tid=0x00316800 nid=0x9fc waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"t2" prio=6 tid=0x02bcf000 nid=0xc70 waiting for monitor entry [0x02f6f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.demo.DeadLock$2.run(DeadLock.java:40)
- waiting to lock <0x22a297a8> (a java.lang.Object)
- locked <0x22a297b0> (a java.lang.Object)
Locked ownable synchronizers:
- None
"t1" prio=6 tid=0x02bce400 nid=0xba0 waiting for monitor entry [0x02f1f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.demo.DeadLock$1.run(DeadLock.java:25)
- waiting to lock <0x22a297b0> (a java.lang.Object)
- locked <0x22a297a8> (a java.lang.Object)
Locked ownable synchronizers:
- None
"Low Memory Detector" daemon prio=6 tid=0x02bb9400 nid=0xa6c runnable [0x00000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"CompilerThread0" daemon prio=10 tid=0x02bb2800 nid=0xcb8 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Attach Listener" daemon prio=10 tid=0x02bb1000 nid=0x7f4 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Signal Dispatcher" daemon prio=10 tid=0x02bd2800 nid=0xd80 runnable [0x00000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"Finalizer" daemon prio=8 tid=0x02bab000 nid=0xe1c in Object.wait() [0x02d3f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x229e1148> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118)
- locked <0x229e1148> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:134)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)
Locked ownable synchronizers:
- None
"Reference Handler" daemon prio=10 tid=0x02ba6800 nid=0xbe0 in Object.wait() [0x02cef000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x229e1048> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:485)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)
- locked <0x229e1048> (a java.lang.ref.Reference$Lock)
Locked ownable synchronizers:
- None
"VM Thread" prio=10 tid=0x02b6a400 nid=0x568 runnable
"VM Periodic Task Thread" prio=10 tid=0x02bc8400 nid=0x75c waiting on condition
JNI global references: 878
Found one Java-level deadlock:
=============================
"t2":
waiting to lock monitor 0x02baaeec (object 0x22a297a8, a java.lang.Object),
which is held by "t1"
"t1":
waiting to lock monitor 0x02baa2bc (object 0x22a297b0, a java.lang.Object),
which is held by "t2"
Java stack information for the threads listed above:
===================================================
"t2":
at com.demo.DeadLock$2.run(DeadLock.java:40)
- waiting to lock <0x22a297a8> (a java.lang.Object)
- locked <0x22a297b0> (a java.lang.Object)
"t1":
at com.demo.DeadLock$1.run(DeadLock.java:25)
- waiting to lock <0x22a297b0> (a java.lang.Object)
- locked <0x22a297a8> (a java.lang.Object)
Found 1 deadlock.
從這個結果文件我們一看到發(fā)現(xiàn)了一個死鎖,具體是線程t2在等待線程t1,而線程t1在等待線程t2造成的,同時也記錄了線程的堆棧和代碼行數(shù),通過這個堆棧和行數(shù)我們就可以去檢查對應的代碼塊,從而發(fā)現(xiàn)問題和解決問題。