摘要:線程的優先級代表線程的優先級為線程代表線程為,而代表該線程對應的操作系統級別的線程。若是有運行圖形界面的環境,也可以使用一些圖形化的工具,例如來生成線程棧文件。使用線程棧定位問題發現死鎖當兩個或多個線程正在等待被對方占有的鎖,死鎖就會發生。
什么是線程棧(thread dump)
線程棧是某個時間點,JVM所有線程的活動狀態的一個匯總;通過線程棧,可以查看某個時間點,各個線程正在做什么,通常使用線程棧來定位軟件運行時的各種問題,例如 CPU 使用率特別高,或者是響應很慢,性能大幅度下滑。
線程棧包含了多個線程的活動信息,一個線程的活動信息通常看起來如下所示:
"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 就是線程的名字,需要注意的是,當使用 Thread 類來創建一條線程,并且沒有指定線程的名字時,這條線程的命名規則為 Thread-i,i 代表數字。如果使用 ThreadFactory 來創建線程,則線程的命名規則為 pool-i-thread-j,i 和 j 分別代表數字。
線程的優先級:prio=10 代表線程的優先級為 10
線程 id:tid=0x00007faac0008800 代表線程 id 為 0x00007faac0008800,而 nid=0x9f0 代表該線程對應的操作系統級別的線程 id。所謂的 nid,換種說法就是 native id。在操作系統中,分為內核級線程和用戶級線程,JVM 的線程是用戶態線程,內核不知情,但每一條 JVM 的線程都會映射到操作系統一條具體的線程
線程的狀態:java.lang.Thread.State: TIMED_WAITING (sleeping) 以及 waiting on condition 代表線程當前的狀態
線程占用的內存地址:[0x00007faac6068000] 代表當前線程占用的內存地址
線程的調用棧:at java.lang.Thread.sleep(Native Method)* 以及它之后的相類似的信息,代表線程的調用棧
回顧線程狀態NEW:線程初創建,未運行
RUNNABLE:線程正在運行,但不一定消耗 CPU
BLOCKED:線程正在等待另外一個線程釋放鎖
WAITING:線程執行了 wait, join, park 方法
TIMED_WAITING:線程調用了sleep, wait, join, park 方法,與 WAITING 狀態不同的是,這些方法帶有表示時間的參數。
例如以下代碼:
public static void main(String[] args) throws InterruptedException { int sum = 0; while (true) { int i = 0; int j = 1; sum = i + j; } }
main 線程對應的線程棧就是
"main" prio=10 tid=0x00007fe1b4008800 nid=0x1292 runnable [0x00007fe1bd88f000] java.lang.Thread.State: RUNNABLE at ThreadDump.main(ThreadDump.java:7)
其狀態為 RUNNABLE
如果是以下代碼,兩個線程會競爭同一個鎖,其中只有一個線程能獲得鎖,然后進行 sleep(time),從而進入 TIMED_WAITING 狀態,另外一個線程由于等待鎖,會進入 BLOCKED 狀態。
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); }
對應的線程棧為:
"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 線程的調用棧里有這么一句 - locked <0x00000000eb8602f8> (a java.lang.Class for ThreadDump),說明它獲得了鎖,并且進行 sleep(sometime) 操作,因此狀態為 TIMED_WAITING。而 t2 線程由于獲取不到鎖,所以在它的調用棧里能看到 - waiting to lock <0x00000000eb8602f8> (a java.lang.Class for ThreadDump),說明它正在等待鎖,因此進入 BLOCKED 狀態。
對于 WAITING 狀態的線程棧,可以使用以下代碼來模擬制造:
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 在某個時間點的線程狀態,因此分析線程棧時,為避免偶然性,有必要多輸出幾份進行分析。以下以 HOT SPOT JVM 為例,首先可以通過以下兩種方式得到 JVM 的進程 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
若系統中沒有 jstack 命令,因為 jstack 命令是 JDK 帶的,而有的環境只安裝了 JRE 環境。則可以用 kill -3 命令來代替,kill -3 pid。Java虛擬機提供了線程轉儲(Thread dump)的后門, 通過這個后門, 可以將線程堆棧打印出來。 這個后門就是通過向Java進程發送一個QUIT信號, Java虛擬機收到該信號之后, 將系
統當前的JAVA線程調用堆棧打印出來。
若是有運行圖形界面的環境,也可以使用一些圖形化的工具,例如 JVisualVM 來生成線程棧文件。
使用線程棧定位問題 發現死鎖當兩個或多個線程正在等待被對方占有的鎖, 死鎖就會發生。 死鎖會導致兩個線程無法繼續運行, 被永遠掛起。
以下代碼會產生死鎖
/** * * * @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) { } } } } }
對應的線程棧是
"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.
可以看到,當發生了死鎖的時候,堆棧中直接打印出了死鎖的信息 Found one Java-level deadlock: ,并給出了分析信息。
要避免死鎖的問題, 唯一的辦法是修改代碼。死鎖可能會導致整個系統的癱瘓, 具體的嚴重程度取決于這些線程執行的是什么性質的功能代碼, 要想恢復系統, 臨時也是唯一的規避辦法是將系統重啟。
定位 CPU 過高的原因首先需要借助操作系統提供的一些工具,來定位消耗 CPU 過高的 native 線程。不同的操作系統,提供的不同的 CPU 統計命令如下所示:
操作系統 | 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進制來表示。 例如上面的例子中3368的十六進制表示為0xd28.在Java線程中查找nid=0xd28即是本地線程對應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)
導致 CPU 過高的原因有以下幾種原因:
Java 代碼死循環
Java 代碼使用了復雜的算法,或者頻繁調用
JVM 自身的代碼導致 CPU 很高
如果在Java線程堆棧中找到了對應的線程ID,并且該Java線程正在執行Native code,說明導致CPU過高的問題代碼在JNI調用中,此時需要打印出 Native 線程的線程棧,在 linux 下,使用 pstack
如果在 native 線程堆棧中可以找到對應的消耗 CPU 過高的線程 id,可以直接定位為 native 代碼的問題。
但是有可能在 native 線程堆棧中找不到對應的消耗 CPU 過高的線程 id,這可能是因為 JNI 調用中重新創建的線程來執行, 那么在 Java 線程堆棧中就不存在該線程的信息,也有可能是虛擬機自身代碼導致的 CPU 過高, 如堆內存使用過高導致的頻繁 FULL GC ,或者 JVM 的 Bug。
性能下降一般是由于資源不足所導致。如果資源不足, 那么有大量的線程在等待資源, 打印的線程堆棧如果發現大量的線程停在同樣的調用上下文上, 那么就說明該系統資源是瓶頸。
導致資源不足的原因可能有:
資源數量配置太少( 如連接池連接配置過少等), 而系統當前的壓力比較大, 資源不足導致了某些線程不能及時獲得資源而等待在那里(即掛起)
獲得資源的線程把持資源時間太久, 導致資源不足,例如以下代碼:
void fun1() { Connection conn = ConnectionPool.getConnection();//獲取一個數據庫連接 //使用該數據庫連接訪問數據庫 //數據庫返回結果,訪問完成 //做其它耗時操作,但這些耗時操作數據庫訪問無關, conn.close(); //釋放連接回池 }
設計不合理導致資源占用時間過久, 如SQL語句設計不恰當, 或者沒有索引導致的數據庫訪問太慢等。
資源用完后, 在某種異常情況下, 沒有關閉或者回池, 導致可用資源泄漏或者減少, 從而導致資源競爭。
定位系統假死原因導致系統掛死的原因有很多, 其中有一個最常見的原因是線程掛死。每次打印線程堆棧, 該線程必然都在同一個調用上下文上, 因此定位該類型的問題原理是,通過打印多次堆棧, 找出對應業務邏輯使用的線程, 通過對比前后打印的堆棧確認該線程執行的代碼段是否一直沒有執行完成。 通過打印多次堆棧, 找到掛起的線程( 即不退出)。
導致線程無法退出的原因可能有:
線程正在執行死循環的代碼
資源不足或者資源泄漏, 造成當前線程阻塞在鎖對象上( 即wait在鎖對象上), 長期得不到喚醒(notify)。
如果當前程序和外部通信, 當外部程序掛起無返回時, 也會導致當前線程掛起。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/69907.html
摘要:這個寫法常常成為系統的瓶頸,如果這個地方恰好是一個性能瓶頸,修改成之后,性能會有大幅的提升。 性能優化的理念 粗略地劃分,代碼可分為 cpu consuming 和 io consuming 兩種類型,即耗 CPU 的和耗 IO 的代碼。如果當前CPU已經能夠接近100%的利用率, 并且代碼業務邏輯無法再簡化, 那么說明該系統的已經達到了性能最大化, 如果再想提高性能, 只能增加處理器...
摘要:萬眾矚目的開源免費代碼部署平臺,終于出預覽版了。驚艷無比,一系列大家無比期待的逐一亮相,代碼發布終于可以不只能選擇,有了一個可自由配置項目,更人性化,支持多用戶多項目多環境同時部署的開源上線部署系統。 萬眾矚目的開源免費代碼部署平臺 walle 2.0,終于出預覽版了。walle 2.0 驚艷無比,一系列大家無比期待的 Feature 逐一亮相,代碼發布終于可以不只能選擇 jenkin...
摘要:從調用棧能清楚發現是這個事件觸發的第二批的讀取動作。然后再去這一個調用棧,發現一個屬性維護了一個開始索引,每次到底部的事件觸發之后,該屬性值都會被累加。這些庫文件一覽在開發者工具查看從后臺加載的庫文件,能發現屬性在此處被硬編碼成。 今天一同事問我這個問題:S/4HANA Fiori應用里的列表,一旦Scroll到底部就會自動向后臺發起新的請求把更多的數據讀取到前臺顯示。 以Produc...
摘要:從調用棧能清楚發現是這個事件觸發的第二批的讀取動作。然后再去這一個調用棧,發現一個屬性維護了一個開始索引,每次到底部的事件觸發之后,該屬性值都會被累加。這些庫文件一覽在開發者工具查看從后臺加載的庫文件,能發現屬性在此處被硬編碼成。 今天一同事問我這個問題:S/4HANA Fiori應用里的列表,一旦Scroll到底部就會自動向后臺發起新的請求把更多的數據讀取到前臺顯示。 以Produc...
閱讀 1197·2023-04-25 17:05
閱讀 3019·2021-11-19 09:40
閱讀 3573·2021-11-18 10:02
閱讀 1750·2021-09-23 11:45
閱讀 3031·2021-08-20 09:36
閱讀 2789·2021-08-13 15:07
閱讀 1141·2019-08-30 15:55
閱讀 2472·2019-08-30 14:11