摘要:線程的優(yōu)先級代表線程的優(yōu)先級為線程代表線程為,而代表該線程對應(yīng)的操作系統(tǒng)級別的線程。若是有運(yùn)行圖形界面的環(huán)境,也可以使用一些圖形化的工具,例如來生成線程棧文件。使用線程棧定位問題發(fā)現(xiàn)死鎖當(dāng)兩個或多個線程正在等待被對方占有的鎖,死鎖就會發(fā)生。
什么是線程棧(thread dump)
線程棧是某個時間點,JVM所有線程的活動狀態(tài)的一個匯總;通過線程棧,可以查看某個時間點,各個線程正在做什么,通常使用線程棧來定位軟件運(yùn)行時的各種問題,例如 CPU 使用率特別高,或者是響應(yīng)很慢,性能大幅度下滑。
線程棧包含了多個線程的活動信息,一個線程的活動信息通常看起來如下所示:
"main" prio=10 tid=0x00007faac0008800 nid=0x9f0 waiting on condition [0x00007faac6068000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at ThreadDump.main(ThreadDump.java:4)
這條線程的線程棧信息包含了以下這些信息:
線程的名字:其中 main 就是線程的名字,需要注意的是,當(dāng)使用 Thread 類來創(chuàng)建一條線程,并且沒有指定線程的名字時,這條線程的命名規(guī)則為 Thread-i,i 代表數(shù)字。如果使用 ThreadFactory 來創(chuàng)建線程,則線程的命名規(guī)則為 pool-i-thread-j,i 和 j 分別代表數(shù)字。
線程的優(yōu)先級:prio=10 代表線程的優(yōu)先級為 10
線程 id:tid=0x00007faac0008800 代表線程 id 為 0x00007faac0008800,而 nid=0x9f0 代表該線程對應(yīng)的操作系統(tǒng)級別的線程 id。所謂的 nid,換種說法就是 native id。在操作系統(tǒng)中,分為內(nèi)核級線程和用戶級線程,JVM 的線程是用戶態(tài)線程,內(nèi)核不知情,但每一條 JVM 的線程都會映射到操作系統(tǒng)一條具體的線程
線程的狀態(tài):java.lang.Thread.State: TIMED_WAITING (sleeping) 以及 waiting on condition 代表線程當(dāng)前的狀態(tài)
線程占用的內(nèi)存地址:[0x00007faac6068000] 代表當(dāng)前線程占用的內(nèi)存地址
線程的調(diào)用棧:at java.lang.Thread.sleep(Native Method)* 以及它之后的相類似的信息,代表線程的調(diào)用棧
回顧線程狀態(tài)NEW:線程初創(chuàng)建,未運(yùn)行
RUNNABLE:線程正在運(yùn)行,但不一定消耗 CPU
BLOCKED:線程正在等待另外一個線程釋放鎖
WAITING:線程執(zhí)行了 wait, join, park 方法
TIMED_WAITING:線程調(diào)用了sleep, wait, join, park 方法,與 WAITING 狀態(tài)不同的是,這些方法帶有表示時間的參數(shù)。
例如以下代碼:
public static void main(String[] args) throws InterruptedException { int sum = 0; while (true) { int i = 0; int j = 1; sum = i + j; } }
main 線程對應(yīng)的線程棧就是
"main" prio=10 tid=0x00007fe1b4008800 nid=0x1292 runnable [0x00007fe1bd88f000] java.lang.Thread.State: RUNNABLE at ThreadDump.main(ThreadDump.java:7)
其狀態(tài)為 RUNNABLE
如果是以下代碼,兩個線程會競爭同一個鎖,其中只有一個線程能獲得鎖,然后進(jìn)行 sleep(time),從而進(jìn)入 TIMED_WAITING 狀態(tài),另外一個線程由于等待鎖,會進(jìn)入 BLOCKED 狀態(tài)。
public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable() { @Override public void run() { try { fun1(); } catch (InterruptedException e) { e.printStackTrace(); } } }); t1.setDaemon(false); t1.setName("MyThread1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { fun2(); } catch (InterruptedException e) { e.printStackTrace(); } } }); t2.setDaemon(false); t2.setName("MyThread2"); t1.start(); t2.start(); */ } private static synchronized void fun1() throws InterruptedException { System.out.println("t1 acquire"); Thread.sleep(Integer.MAX_VALUE); } private static synchronized void fun2() throws InterruptedException { System.out.println("t2 acquire"); Thread.sleep(Integer.MAX_VALUE); }
對應(yīng)的線程棧為:
"MyThread2" prio=10 tid=0x00007ff1e40b1000 nid=0x12eb waiting for monitor entry [0x00007ff1e07f6000] java.lang.Thread.State: BLOCKED (on object monitor) at ThreadDump.fun2(ThreadDump.java:45) - waiting to lock <0x00000000eb8602f8> (a java.lang.Class for ThreadDump) at ThreadDump.access$100(ThreadDump.java:1) at ThreadDump$2.run(ThreadDump.java:25) at java.lang.Thread.run(Thread.java:745) "MyThread1" prio=10 tid=0x00007ff1e40af000 nid=0x12ea waiting on condition [0x00007ff1e08f7000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at ThreadDump.fun1(ThreadDump.java:41) - locked <0x00000000eb8602f8> (a java.lang.Class for ThreadDump) at ThreadDump.access$000(ThreadDump.java:1) at ThreadDump$1.run(ThreadDump.java:10) at java.lang.Thread.run(Thread.java:745)
可以看到,t1 線程的調(diào)用棧里有這么一句 - locked <0x00000000eb8602f8> (a java.lang.Class for ThreadDump),說明它獲得了鎖,并且進(jìn)行 sleep(sometime) 操作,因此狀態(tài)為 TIMED_WAITING。而 t2 線程由于獲取不到鎖,所以在它的調(diào)用棧里能看到 - waiting to lock <0x00000000eb8602f8> (a java.lang.Class for ThreadDump),說明它正在等待鎖,因此進(jìn)入 BLOCKED 狀態(tài)。
對于 WAITING 狀態(tài)的線程棧,可以使用以下代碼來模擬制造:
private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { synchronized (lock) { lock.wait(); } }
得到的線程棧為:
"main" prio=10 tid=0x00007f1fdc008800 nid=0x13fe in Object.wait() [0x00007f1fe1fec000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000000eb860640> (a java.lang.Object) at java.lang.Object.wait(Object.java:503) at ThreadDump.main(ThreadDump.java:7) - locked <0x00000000eb860640> (a java.lang.Object)如何輸出線程棧
由于線程棧反映的是 JVM 在某個時間點的線程狀態(tài),因此分析線程棧時,為避免偶然性,有必要多輸出幾份進(jìn)行分析。以下以 HOT SPOT JVM 為例,首先可以通過以下兩種方式得到 JVM 的進(jìn)程 ID。
jps 命令
[root@localhost ~]# jps 5163 ThreadDump 5173 Jps
ps -ef | grep java
[root@localhost ~]# ps -ef | grep java root 5163 2479 0 01:18 pts/0 00:00:00 java ThreadDump root 5185 2553 0 01:18 pts/1 00:00:00 grep --color=auto java
接下來通過 JDK 自帶的 jstack 命令
[root@localhost ~]# jstack 5163 2017-04-21 01:19:41 Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.79-b02 mixed mode): "Attach Listener" daemon prio=10 tid=0x00007f72b8001000 nid=0x144c waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Service Thread" daemon prio=10 tid=0x00007f72d4095000 nid=0x1433 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread1" daemon prio=10 tid=0x00007f72d4092800 nid=0x1432 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread0" daemon prio=10 tid=0x00007f72d4090000 nid=0x1431 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Signal Dispatcher" daemon prio=10 tid=0x00007f72d408e000 nid=0x1430 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Finalizer" daemon prio=10 tid=0x00007f72d4065000 nid=0x142f in Object.wait() [0x00007f72d9b83000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000000eb804858> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135) - locked <0x00000000eb804858> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209) "Reference Handler" daemon prio=10 tid=0x00007f72d4063000 nid=0x142e in Object.wait() [0x00007f72d9c84000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000000eb804470> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:503) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133) - locked <0x00000000eb804470> (a java.lang.ref.Reference$Lock) "main" prio=10 tid=0x00007f72d4008800 nid=0x142c in Object.wait() [0x00007f72dc971000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000000eb860620> (a java.lang.Object) at java.lang.Object.wait(Object.java:503) at ThreadDump.main(ThreadDump.java:7) - locked <0x00000000eb860620> (a java.lang.Object) "VM Thread" prio=10 tid=0x00007f72d405e800 nid=0x142d runnable "VM Periodic Task Thread" prio=10 tid=0x00007f72d40a0000 nid=0x1434 waiting on condition JNI global references: 107
即可將線程棧輸出到控制臺。若輸出信息過多,在控制臺上不方便分析,則可以將輸出信息重定向到文件中,如下所示:
jstack 5163 > thread.stack
若系統(tǒng)中沒有 jstack 命令,因為 jstack 命令是 JDK 帶的,而有的環(huán)境只安裝了 JRE 環(huán)境。則可以用 kill -3 命令來代替,kill -3 pid。Java虛擬機(jī)提供了線程轉(zhuǎn)儲(Thread dump)的后門, 通過這個后門, 可以將線程堆棧打印出來。 這個后門就是通過向Java進(jìn)程發(fā)送一個QUIT信號, Java虛擬機(jī)收到該信號之后, 將系
統(tǒng)當(dāng)前的JAVA線程調(diào)用堆棧打印出來。
若是有運(yùn)行圖形界面的環(huán)境,也可以使用一些圖形化的工具,例如 JVisualVM 來生成線程棧文件。
使用線程棧定位問題 發(fā)現(xiàn)死鎖當(dāng)兩個或多個線程正在等待被對方占有的鎖, 死鎖就會發(fā)生。 死鎖會導(dǎo)致兩個線程無法繼續(xù)運(yùn)行, 被永遠(yuǎn)掛起。
以下代碼會產(chǎn)生死鎖
/** * * * @author beanlam * @version 1.0 * */ public class ThreadDump { public static void main(String[] args) throws InterruptedException { Object lock1 = new Object(); Object lock2 = new Object(); new Thread1(lock1, lock2).start(); new Thread2(lock1, lock2).start(); } private static class Thread1 extends Thread { Object lock1 = null; Object lock2 = null; public Thread1(Object lock1, Object lock2) { this.lock1 = lock1; this.lock2 = lock2; this.setName(getClass().getSimpleName()); } public void run() { synchronized (lock1) { try { Thread.sleep(2); } catch(Exception e) { e.printStackTrace(); } synchronized (lock2) { } } } } private static class Thread2 extends Thread { Object lock1 = null; Object lock2 = null; public Thread2(Object lock1, Object lock2) { this.lock1 = lock1; this.lock2 = lock2; this.setName(getClass().getSimpleName()); } public void run() { synchronized (lock2) { try { Thread.sleep(2); } catch(Exception e) { e.printStackTrace(); } synchronized (lock1) { } } } } }
對應(yīng)的線程棧是
"Thread2" prio=10 tid=0x00007f9bf40a1000 nid=0x1472 waiting for monitor entry [0x00007f9bf8944000] java.lang.Thread.State: BLOCKED (on object monitor) at ThreadDump$Thread2.run(ThreadDump.java:63) - waiting to lock <0x00000000eb860498> (a java.lang.Object) - locked <0x00000000eb8604a8> (a java.lang.Object) "Thread1" prio=10 tid=0x00007f9bf409f000 nid=0x1471 waiting for monitor entry [0x00007f9bf8a45000] java.lang.Thread.State: BLOCKED (on object monitor) at ThreadDump$Thread1.run(ThreadDump.java:38) - waiting to lock <0x00000000eb8604a8> (a java.lang.Object) - locked <0x00000000eb860498> (a java.lang.Object) Found one Java-level deadlock: ============================= "Thread2": waiting to lock monitor 0x00007f9be4004f88 (object 0x00000000eb860498, a java.lang.Object), which is held by "Thread1" "Thread1": waiting to lock monitor 0x00007f9be40062c8 (object 0x00000000eb8604a8, a java.lang.Object), which is held by "Thread2" Java stack information for the threads listed above: =================================================== "Thread2": at ThreadDump$Thread2.run(ThreadDump.java:63) - waiting to lock <0x00000000eb860498> (a java.lang.Object) - locked <0x00000000eb8604a8> (a java.lang.Object) "Thread1": at ThreadDump$Thread1.run(ThreadDump.java:38) - waiting to lock <0x00000000eb8604a8> (a java.lang.Object) - locked <0x00000000eb860498> (a java.lang.Object) Found 1 deadlock.
可以看到,當(dāng)發(fā)生了死鎖的時候,堆棧中直接打印出了死鎖的信息 Found one Java-level deadlock: ,并給出了分析信息。
要避免死鎖的問題, 唯一的辦法是修改代碼。死鎖可能會導(dǎo)致整個系統(tǒng)的癱瘓, 具體的嚴(yán)重程度取決于這些線程執(zhí)行的是什么性質(zhì)的功能代碼, 要想恢復(fù)系統(tǒng), 臨時也是唯一的規(guī)避辦法是將系統(tǒng)重啟。
定位 CPU 過高的原因首先需要借助操作系統(tǒng)提供的一些工具,來定位消耗 CPU 過高的 native 線程。不同的操作系統(tǒng),提供的不同的 CPU 統(tǒng)計命令如下所示:
操作系統(tǒng) | solaris | linux | aix |
---|---|---|---|
命令名稱 | prstat -L |
top -p |
ps -emo THREAD |
以 Linux 為例,首先通過 top -p
top - 02:04:54 up 2:43, 3 users, load average: 0.10, 0.05, 0.05 Threads: 13 total, 0 running, 13 sleeping, 0 stopped, 0 zombie %Cpu(s): 97.74 us, 0.2 sy, 0.0 ni, 2.22 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem: 1003456 total, 722012 used, 281444 free, 0 buffers KiB Swap: 2097148 total, 62872 used, 2034276 free. 68880 cached Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3368 zmw2 25 0 256m 9620 6460 R 93.3 0.7 5:42.06 java 3369 zmw2 15 0 256m 9620 6460 S 0.0 0.7 0:00.00 java 3370 zmw2 15 0 256m 9620 6460 S 0.0 0.7 0:00.00 java 3371 zmw2 15 0 256m 9620 6460 S 0.0 0.7 0:00.00 java 3372 zmw2 15 0 256m 9620 6460 S 0.0 0.7 0:00.00 java 3373 zmw2 15 0 256m 9620 6460 S 0.0 0.7 0:00.00 java 3374 zmw2 15 0 256m 9620 6460 S 0.0 0.7 0:00.00 java 3375 zmw2 15 0 256m 9620 6460 S 0.0 0.7 0:00.00 java
這個命令輸出的 PID 代表的是 native 線程的 id,如上所示,id 為 3368 的 native 線程消耗 CPU 最高。在Java Thread Dump文件中, 每個線程都有tid=...nid=...的屬性, 其中nid就是native thread id, 只不過nid中用16進(jìn)制來表示。 例如上面的例子中3368的十六進(jìn)制表示為0xd28.在Java線程中查找nid=0xd28即是本地線程對應(yīng)Java線程。
"main" prio=1 tid=0x0805c988 nid=0xd28 runnable [0xfff65000..0xfff659c8] at java.lang.String.indexOf(String.java:1352) at java.io.PrintStream.write(PrintStream.java:460) - locked <0xc8bf87d8> (a java.io.PrintStream) at java.io.PrintStream.print(PrintStream.java:602) at MyTest.fun2(MyTest.java:16) - locked <0xc8c1a098> (a java.lang.Object) at MyTest.fun1(MyTest.java:8) - locked <0xc8c1a090> (a java.lang.Object) at MyTest.main(MyTest.java:26)
導(dǎo)致 CPU 過高的原因有以下幾種原因:
Java 代碼死循環(huán)
Java 代碼使用了復(fù)雜的算法,或者頻繁調(diào)用
JVM 自身的代碼導(dǎo)致 CPU 很高
如果在Java線程堆棧中找到了對應(yīng)的線程ID,并且該Java線程正在執(zhí)行Native code,說明導(dǎo)致CPU過高的問題代碼在JNI調(diào)用中,此時需要打印出 Native 線程的線程棧,在 linux 下,使用 pstack
如果在 native 線程堆棧中可以找到對應(yīng)的消耗 CPU 過高的線程 id,可以直接定位為 native 代碼的問題。
但是有可能在 native 線程堆棧中找不到對應(yīng)的消耗 CPU 過高的線程 id,這可能是因為 JNI 調(diào)用中重新創(chuàng)建的線程來執(zhí)行, 那么在 Java 線程堆棧中就不存在該線程的信息,也有可能是虛擬機(jī)自身代碼導(dǎo)致的 CPU 過高, 如堆內(nèi)存使用過高導(dǎo)致的頻繁 FULL GC ,或者 JVM 的 Bug。
性能下降一般是由于資源不足所導(dǎo)致。如果資源不足, 那么有大量的線程在等待資源, 打印的線程堆棧如果發(fā)現(xiàn)大量的線程停在同樣的調(diào)用上下文上, 那么就說明該系統(tǒng)資源是瓶頸。
導(dǎo)致資源不足的原因可能有:
資源數(shù)量配置太少( 如連接池連接配置過少等), 而系統(tǒng)當(dāng)前的壓力比較大, 資源不足導(dǎo)致了某些線程不能及時獲得資源而等待在那里(即掛起)
獲得資源的線程把持資源時間太久, 導(dǎo)致資源不足,例如以下代碼:
void fun1() { Connection conn = ConnectionPool.getConnection();//獲取一個數(shù)據(jù)庫連接 //使用該數(shù)據(jù)庫連接訪問數(shù)據(jù)庫 //數(shù)據(jù)庫返回結(jié)果,訪問完成 //做其它耗時操作,但這些耗時操作數(shù)據(jù)庫訪問無關(guān), conn.close(); //釋放連接回池 }
設(shè)計不合理導(dǎo)致資源占用時間過久, 如SQL語句設(shè)計不恰當(dāng), 或者沒有索引導(dǎo)致的數(shù)據(jù)庫訪問太慢等。
資源用完后, 在某種異常情況下, 沒有關(guān)閉或者回池, 導(dǎo)致可用資源泄漏或者減少, 從而導(dǎo)致資源競爭。
定位系統(tǒng)假死原因導(dǎo)致系統(tǒng)掛死的原因有很多, 其中有一個最常見的原因是線程掛死。每次打印線程堆棧, 該線程必然都在同一個調(diào)用上下文上, 因此定位該類型的問題原理是,通過打印多次堆棧, 找出對應(yīng)業(yè)務(wù)邏輯使用的線程, 通過對比前后打印的堆棧確認(rèn)該線程執(zhí)行的代碼段是否一直沒有執(zhí)行完成。 通過打印多次堆棧, 找到掛起的線程( 即不退出)。
導(dǎo)致線程無法退出的原因可能有:
線程正在執(zhí)行死循環(huán)的代碼
資源不足或者資源泄漏, 造成當(dāng)前線程阻塞在鎖對象上( 即wait在鎖對象上), 長期得不到喚醒(notify)。
如果當(dāng)前程序和外部通信, 當(dāng)外部程序掛起無返回時, 也會導(dǎo)致當(dāng)前線程掛起。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/69907.html
摘要:這個寫法常常成為系統(tǒng)的瓶頸,如果這個地方恰好是一個性能瓶頸,修改成之后,性能會有大幅的提升。 性能優(yōu)化的理念 粗略地劃分,代碼可分為 cpu consuming 和 io consuming 兩種類型,即耗 CPU 的和耗 IO 的代碼。如果當(dāng)前CPU已經(jīng)能夠接近100%的利用率, 并且代碼業(yè)務(wù)邏輯無法再簡化, 那么說明該系統(tǒng)的已經(jīng)達(dá)到了性能最大化, 如果再想提高性能, 只能增加處理器...
摘要:萬眾矚目的開源免費(fèi)代碼部署平臺,終于出預(yù)覽版了。驚艷無比,一系列大家無比期待的逐一亮相,代碼發(fā)布終于可以不只能選擇,有了一個可自由配置項目,更人性化,支持多用戶多項目多環(huán)境同時部署的開源上線部署系統(tǒng)。 萬眾矚目的開源免費(fèi)代碼部署平臺 walle 2.0,終于出預(yù)覽版了。walle 2.0 驚艷無比,一系列大家無比期待的 Feature 逐一亮相,代碼發(fā)布終于可以不只能選擇 jenkin...
摘要:從調(diào)用棧能清楚發(fā)現(xiàn)是這個事件觸發(fā)的第二批的讀取動作。然后再去這一個調(diào)用棧,發(fā)現(xiàn)一個屬性維護(hù)了一個開始索引,每次到底部的事件觸發(fā)之后,該屬性值都會被累加。這些庫文件一覽在開發(fā)者工具查看從后臺加載的庫文件,能發(fā)現(xiàn)屬性在此處被硬編碼成。 今天一同事問我這個問題:S/4HANA Fiori應(yīng)用里的列表,一旦Scroll到底部就會自動向后臺發(fā)起新的請求把更多的數(shù)據(jù)讀取到前臺顯示。 以Produc...
摘要:從調(diào)用棧能清楚發(fā)現(xiàn)是這個事件觸發(fā)的第二批的讀取動作。然后再去這一個調(diào)用棧,發(fā)現(xiàn)一個屬性維護(hù)了一個開始索引,每次到底部的事件觸發(fā)之后,該屬性值都會被累加。這些庫文件一覽在開發(fā)者工具查看從后臺加載的庫文件,能發(fā)現(xiàn)屬性在此處被硬編碼成。 今天一同事問我這個問題:S/4HANA Fiori應(yīng)用里的列表,一旦Scroll到底部就會自動向后臺發(fā)起新的請求把更多的數(shù)據(jù)讀取到前臺顯示。 以Produc...
閱讀 1186·2023-04-25 17:05
閱讀 3011·2021-11-19 09:40
閱讀 3545·2021-11-18 10:02
閱讀 1740·2021-09-23 11:45
閱讀 3022·2021-08-20 09:36
閱讀 2783·2021-08-13 15:07
閱讀 1133·2019-08-30 15:55
閱讀 2459·2019-08-30 14:11