線程堆棧也稱做線程調用堆棧。Java線程堆棧是虛擬機中線程(包括鎖)狀態的一個瞬間快照,即系統在某個時刻全部線程的運行狀態,包括每個線程的調用堆棧,鎖的持有狀況等信息。對於已經消失而又沒留有痕跡的信息,線程堆棧是沒法進行歷史追蹤的。
線程堆棧的信息包括:(1)線程的名字,id,線程的數量等(2)線程的運行狀態,鎖的狀態(鎖被哪一個線程持有,哪一個線程再等待鎖等)(3)調用堆棧(即函數的調用層次關係),調用堆棧包括完整的類名,所執行的方法,源代碼的行數。
藉助線程堆棧能夠分析:線程死鎖、鎖爭用、死循環、識別耗時操做、穩定性分析和性能分析等問題。java
Java虛擬機提供了線程轉儲(Thread dump)的後門。經過以下的命令行方式向Java進程請求堆棧輸出:
window 在運行Java的控制窗口上按ctrl + break組合鍵
linux 使用kill -3 pidlinux
Java源代碼算法
public class MyTest { Object obj1 = new Object(); Object obj2 = new Object(); public void fun1() { synchronized (obj1) { fun2(); } } public void fun2() { synchronized (obj2) { while (true) { } } } public static void main(String[] args) { MyTest mt = new MyTest(); mt.fun1(); } }
編譯運行,kill -3 pid數據庫
2019-05-30 14:27:14 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.111-b14 mixed mode): "Service Thread" #17 daemon prio=9 os_prio=0 tid=0x00007f41680f5800 nid=0x562e runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C1 CompilerThread11" #16 daemon prio=9 os_prio=0 tid=0x00007f41680f2800 nid=0x562d waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C1 CompilerThread10" #15 daemon prio=9 os_prio=0 tid=0x00007f41680f0800 nid=0x562c waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C1 CompilerThread9" #14 daemon prio=9 os_prio=0 tid=0x00007f41680ee000 nid=0x562b waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C1 CompilerThread8" #13 daemon prio=9 os_prio=0 tid=0x00007f41680ec000 nid=0x562a waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread7" #12 daemon prio=9 os_prio=0 tid=0x00007f41680ea000 nid=0x5629 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread6" #11 daemon prio=9 os_prio=0 tid=0x00007f41680e8000 nid=0x5628 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread5" #10 daemon prio=9 os_prio=0 tid=0x00007f41680e5800 nid=0x5627 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread4" #9 daemon prio=9 os_prio=0 tid=0x00007f41680db800 nid=0x5626 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread3" #8 daemon prio=9 os_prio=0 tid=0x00007f41680d9800 nid=0x5625 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread2" #7 daemon prio=9 os_prio=0 tid=0x00007f41680d7000 nid=0x5624 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f41680d5800 nid=0x5623 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f41680d2800 nid=0x5622 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f41680d1000 nid=0x5621 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f416809e000 nid=0x5620 in Object.wait() [0x00007f4144b7a000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000005c8b08e98> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143) - locked <0x00000005c8b08e98> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209) "Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f4168099800 nid=0x561f in Object.wait() [0x00007f40b0cfe000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000005c8b06b40> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:502) at java.lang.ref.Reference.tryHandlePending(Reference.java:191) - locked <0x00000005c8b06b40> (a java.lang.ref.Reference$Lock) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153) "main" #1 prio=5 os_prio=0 tid=0x00007f4168007800 nid=0x560b runnable [0x00007f4170107000] ---只有main線程屬於Java用戶線程,其餘都是由虛擬機自動建立的線程 java.lang.Thread.State: RUNNABLE at com.huawei.diagnose.thread.MyTest.fun2(MyTest.java:17) ---當前線程調用上下文,即從哪一個函數中調用到哪一個函數中(從下往上看),正執行到哪一個類的哪一行。 - locked <0x00000005c8b5dce0> (a java.lang.Object) ---"main" #1 prio=5 os_prio=0 tid=0x00007f4168007800 nid=0x560b runnable [0x00007f4170107000] at com.huawei.diagnose.thread.MyTest.fun1(MyTest.java:11) ---main線程名稱,prio=5線程優先級,tid=0x00007f4168007800線程id,nid=0x560b線程對應的本地線程id號(Native Thread ID),runnable線程狀態 - locked <0x00000005c8b5dcd0> (a java.lang.Object) ---線程main已經佔有了鎖<0x00000005c8b5dcd0>,其中<0x00000005c8b5dcd0> ---標示鎖ID,這個ID是系統自動產生的,咱們只須要知道每次打印的堆棧,同一個ID表示是同一個鎖便可 at com.huawei.diagnose.thread.MyTest.main(MyTest.java:25) "VM Thread" os_prio=0 tid=0x00007f4168092000 nid=0x561e runnable "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f416801c800 nid=0x560c runnable "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f416801e800 nid=0x560d runnable "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f4168020000 nid=0x560e runnable "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f4168022000 nid=0x560f runnable "GC task thread#4 (ParallelGC)" os_prio=0 tid=0x00007f4168023800 nid=0x5610 runnable "GC task thread#5 (ParallelGC)" os_prio=0 tid=0x00007f4168025800 nid=0x5611 runnable "GC task thread#6 (ParallelGC)" os_prio=0 tid=0x00007f4168027000 nid=0x5612 runnable "GC task thread#7 (ParallelGC)" os_prio=0 tid=0x00007f4168029000 nid=0x5613 runnable "GC task thread#8 (ParallelGC)" os_prio=0 tid=0x00007f416802a800 nid=0x5614 runnable "GC task thread#9 (ParallelGC)" os_prio=0 tid=0x00007f416802c800 nid=0x5615 runnable "GC task thread#10 (ParallelGC)" os_prio=0 tid=0x00007f416802e000 nid=0x5616 runnable "GC task thread#11 (ParallelGC)" os_prio=0 tid=0x00007f4168030000 nid=0x5617 runnable "GC task thread#12 (ParallelGC)" os_prio=0 tid=0x00007f4168031800 nid=0x5618 runnable "GC task thread#13 (ParallelGC)" os_prio=0 tid=0x00007f4168033800 nid=0x5619 runnable "GC task thread#14 (ParallelGC)" os_prio=0 tid=0x00007f4168035000 nid=0x561a runnable "GC task thread#15 (ParallelGC)" os_prio=0 tid=0x00007f4168037000 nid=0x561b runnable "GC task thread#16 (ParallelGC)" os_prio=0 tid=0x00007f4168038800 nid=0x561c runnable "GC task thread#17 (ParallelGC)" os_prio=0 tid=0x00007f416803a800 nid=0x561d runnable "VM Periodic Task Thread" os_prio=0 tid=0x00007f41680f8800 nid=0x562f waiting on condition
其中,「線程對應的本地線程ID號」是指的「本地線程」是指該Java線程所對應的虛擬機中的本地線程。Java語言中的現象是依附於Java虛擬機中的本地線程來運行的,其實是本地線程在執行Java線程代碼。Java代碼中建立一個線程,虛擬機在運行期間執行到該建立線程的字節碼時,就會建立一個對應的本地線程,而這個本地線程纔是真正的線程實體。ubuntu
如何把Java線程堆棧裏面的線程與本地線程對應上?網絡
以這個main線程爲例多線程
"main" #1 prio=5 os_prio=0 tid=0x00007f5444007800 nid=0x581c runnable [0x00007f544b1ff000] java.lang.Thread.State: RUNNABLE at com.huawei.diagnose.thread.MyTest.fun2(MyTest.java:17) - locked <0x00000005c8b5dce0> (a java.lang.Object) at com.huawei.diagnose.thread.MyTest.fun1(MyTest.java:11) - locked <0x00000005c8b5dcd0> (a java.lang.Object) at com.huawei.diagnose.thread.MyTest.main(MyTest.java:25)
nid=0x581c的十進制爲22556socket
(1)查看Java程序的進程號jps,好比22555
(2)top -p 22555,進入top界面,而後按H,能夠線程該Java虛擬機裏面全部的線程,發現有一個22556的pid就是main所對應的本地線程數據庫設計
root@ubuntu-blade2:/sdf# top -p 22555 top - 14:57:57 up 107 days, 3:37, 3 users, load average: 0.99, 0.60, 0.45 Tasks: 38 total, 1 running, 37 sleeping, 0 stopped, 0 zombie Cpu(s): 4.4%us, 0.1%sy, 0.0%ni, 95.5%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Mem: 98956100k total, 54769432k used, 44186668k free, 1350856k buffers Swap: 100632572k total, 0k used, 100632572k free, 47744612k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 22556 root 20 0 28.2g 22m 10m R 99.8 0.0 0:43.70 java 22555 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22557 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22558 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22559 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22560 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22561 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22562 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22563 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22564 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22565 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22566 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22567 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22568 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22569 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22570 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22571 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22572 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22573 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22574 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22575 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22576 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22577 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22578 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22579 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22580 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22581 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22582 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22583 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22584 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22585 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22586 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22587 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22588 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22589 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22590 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22591 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.00 java 22592 root 20 0 28.2g 22m 10m S 0.0 0.0 0:00.02 java
從分析能夠看出,Java線程設計上和本地線程指的是同一個實體,只有本地線程纔是真正的線程實體,Java線程實際上就是指這個本地線程,它並非另外存在的實體。ide
"main" #1 prio=5 os_prio=0 tid=0x00007f5444007800 nid=0x581c runnable [0x00007f544b1ff000]
中的runnable標示當前線程處於運行狀態。這個runnable狀態是從虛擬機的角度來看的,標示這個線程正在運行。可是處於runnable狀態的線程不必定真的消耗CPU。
處於Runnable的線程只能說明該線程沒有阻塞在Java的wait或sleep方法上,同時也沒等待在鎖上面。
可是若是該線程調用了本地方法,而本地方法處於等待狀態,這個時候虛擬機是不知道本地代碼中發生了什麼,此時儘管當前線程實際上也是阻塞的狀態,但實際上顯示出來的仍是Runnable狀態,這種狀況下是不消耗CPU的。
也就是說,Runnable狀態不意味這個線程正在消耗CPU。
堆棧信息裏面包含Native Method或Compiled Code
表示一個是本地方法JNI,一個是該class的方法已經被JIT編譯成了本地代碼
wait當線程執行到wait方法上,當前線程會釋放監視鎖,此時其餘線程能夠佔用該鎖,一旦wait方法執行完成,當前線程會繼續參與鎖的競爭,直到執行完該鎖的做用域。
正是因爲wait的這個特性:一旦執行到一個鎖的wait方法,該線程就會釋放這個鎖,因此能夠有多個線程一塊兒進入到同步塊中。
sleep該方法與鎖操做無關,若是該方法剛好在一個鎖的保護範圍內,當前線程即便在執行sleep的時候,仍然繼續保持監視鎖。也就說說該方法其實是和鎖操做無關的。
鎖的狀態目前主要有三種:
(1)當一個線程佔有一個鎖的時候,線程堆棧會打印locked <xxx>
(2)當一個線程正在等待其餘線程釋放該鎖,線程堆棧會打印waiting to lock <xxx>
(3)當一個線程佔有一個鎖,但又執行到該鎖的wait上,線程堆棧會先打印locked <xxx>,而後又會打印waiting on <xxx>
java源代碼
public class ThreadTest { public static void main(String[] args) { Object share = new Object(); TestThreadLocked thread1 = new TestThreadLocked(share); thread1.start(); TestThreadWaitingTo thread2 = new TestThreadWaitingTo(share); thread2.start(); TestThreadWaitingOn thread3 = new TestThreadWaitingOn(); thread3.start(); } } class TestThreadLocked extends Thread { Object lock = null; public TestThreadLocked(Object lock) { this.lock = lock; this.setName(this.getClass().getName()); } @Override public void run() { fun(); } public void fun() { synchronized (lock) { funLongTime(); } } public void funLongTime() { try { Thread.sleep(20000); } catch (Exception e) { e.printStackTrace(); } } } class TestThreadWaitingOn extends Thread { Object lock = new Object(); public TestThreadWaitingOn() { this.setName(this.getClass().getName()); } @Override public void run() { fun(); } public void fun() { synchronized (lock) { funWait(); } } public void funWait() { try { lock.wait(100000); } catch (Exception e) { e.printStackTrace(); } } } class TestThreadWaitingTo extends Thread { Object lock = null; public TestThreadWaitingTo(Object lock) { this.lock = lock; this.setName(this.getClass().getName()); } @Override public void run() { fun(); } public void fun() { synchronized (lock) { funLongTime(); } } public void funLongTime() { try { Thread.sleep(20000); } catch (Exception e) { e.printStackTrace(); } } }
線程堆棧以下:
"com.huawei.diagnose.thread.TestThreadWaitingOn" #20 prio=5 os_prio=0 tid=0x00007f2a1010e000 nid=0x5cb7 in Object.wait() [0x00007f29cf2f1000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000005c8b630c0> (a java.lang.Object) --執行到鎖0x00000005c8b630c0的wait方法 at java.lang.Object.wait(Object.java:502) at com.huawei.diagnose.thread.TestThreadWaitingOn.funWait(ThreadTest.java:66) at com.huawei.diagnose.thread.TestThreadWaitingOn.fun(ThreadTest.java:60) - locked <0x00000005c8b630c0> (a java.lang.Object) --已經佔有了鎖0x00000005c8b630c0 at com.huawei.diagnose.thread.TestThreadWaitingOn.run(ThreadTest.java:55) "com.huawei.diagnose.thread.TestThreadWaitingTo" #19 prio=5 os_prio=0 tid=0x00007f2a1010c800 nid=0x5cb6 waiting for monitor entry [0x00007f29cf3f2000] java.lang.Thread.State: BLOCKED (on object monitor) at com.huawei.diagnose.thread.TestThreadWaitingTo.fun(ThreadTest.java:87) - waiting to lock <0x00000005c8b5dbf8> (a java.lang.Object) --鎖0x00000005c8b5dbf8已經被TestThreadLocked佔有,TestThreadWaitingTo只能等待 at com.huawei.diagnose.thread.TestThreadWaitingTo.run(ThreadTest.java:82) "com.huawei.diagnose.thread.TestThreadLocked" #18 prio=5 os_prio=0 tid=0x00007f2a1010a800 nid=0x5cb5 waiting on condition [0x00007f29cf4f3000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at com.huawei.diagnose.thread.TestThreadLocked.funLongTime(ThreadTest.java:40) at com.huawei.diagnose.thread.TestThreadLocked.fun(ThreadTest.java:34) - locked <0x00000005c8b5dbf8> (a java.lang.Object) --該線程佔有鎖0x00000005c8b5dbf8 at com.huawei.diagnose.thread.TestThreadLocked.run(ThreadTest.java:30)
Java的線程狀態有以下幾類:
RUNNABLE從虛擬機的角度看,線程處於正在運行狀態。處於Runnable的線程不必定消耗CPU。
好比線程正在從網絡讀取數據,儘管線程顯示weightRunnable狀態,但實際上網絡IO,線程絕大多數時間是被掛起,只有當數據到達以後,線程才被從新喚醒,掛起發生在本地代碼中,虛擬機根本不知道,像這種socket IO操做,不會消耗大量的CPU,由於大多時間在等待,只有數據到來以後,才消耗一點CPU。
TIME_WAITING(on object monitor)表示當前線程被掛起一段時間,說明該線程正在執行obj.wait(int time)方法。
TIMED_WAITING(sleeping)表示當前線程被掛起一段時間,即正在執行Thread.sleep(int time)方法。
TIMED_WAITING(parking)當前線程被掛起一段時時間,即正在執行LockSupport.parkNanos()。
WAITING(on object monitor)當前線程被掛起,即正在執行obj.wait()方法,無參數的wait方法
總結:處於TIMED_WAITING,WAITING狀態的線程必定不消耗CPU,處於RUNNABLE的線程,要結合當前線程代碼的性質判斷,是否消耗CPU
(1)若是是純Java運算代碼,則消耗CPU
(2)若是是網絡IO,不多消耗CPU
(3)若是是本地代碼,結合本地代碼的性質判斷,若是是純運算代碼,則消耗CPU,若是被掛起,則不消耗CPU,若是是IO,則不怎麼消耗CPU。
看堆棧通常從三個視角來分析:堆棧的局部信息,一次堆棧的統計信息(全局信息),多個堆棧的對比信息(打印一次堆棧是一個切面,打印屢次堆棧就是立體了)。
Java源代碼
public class TestDeadLock { public static void main(String[] args) { Object lock1 = new Object(); Object lock2 = new Object(); TestThread1 thread1 = new TestThread1(lock1, lock2); thread1.start(); TestThread2 thread2 = new TestThread2(lock1, lock2); thread2.start(); } static class TestThread1 extends Thread { Object lock1 = null; Object lock2 = null; public TestThread1(Object lock1, Object lock2) { this.lock1 = lock1; this.lock2 = lock2; this.setName(this.getClass().getName()); } @Override public void run() { fun(); } public void fun() { synchronized (lock1) { try { Thread.sleep(2); } catch (Exception e) { e.printStackTrace(); } new Throwable().printStackTrace(); synchronized (lock2) { } } } } static class TestThread2 extends Thread { Object lock1 = null; Object lock2 = null; public TestThread2(Object lock1, Object lock2) { this.lock1 = lock1; this.lock2 = lock2; this.setName(this.getClass().getName()); } @Override public void run() { fun(); } public void fun() { synchronized (lock2) { try { Thread.sleep(2); } catch (Exception e) { e.printStackTrace(); } synchronized (lock1) { } } } } }
堆棧以下:
Found one Java-level deadlock: ============================= "com.huawei.diagnose.thread.TestDeadLock$TestThread2": waiting to lock monitor 0x00007f9db0004e28 (object 0x00000005c8b5dc80, a java.lang.Object), which is held by "com.huawei.diagnose.thread.TestDeadLock$TestThread1" "com.huawei.diagnose.thread.TestDeadLock$TestThread1": waiting to lock monitor 0x00007f9db00062c8 (object 0x00000005c8b5dc90, a java.lang.Object), which is held by "com.huawei.diagnose.thread.TestDeadLock$TestThread2" Java stack information for the threads listed above: =================================================== "com.huawei.diagnose.thread.TestDeadLock$TestThread2": at com.huawei.diagnose.thread.TestDeadLock$TestThread2.fun(TestDeadLock.java:70) - waiting to lock <0x00000005c8b5dc80> (a java.lang.Object) - locked <0x00000005c8b5dc90> (a java.lang.Object) at com.huawei.diagnose.thread.TestDeadLock$TestThread2.run(TestDeadLock.java:59) "com.huawei.diagnose.thread.TestDeadLock$TestThread1": at com.huawei.diagnose.thread.TestDeadLock$TestThread1.fun(TestDeadLock.java:41) - waiting to lock <0x00000005c8b5dc90> (a java.lang.Object) - locked <0x00000005c8b5dc80> (a java.lang.Object) at com.huawei.diagnose.thread.TestDeadLock$TestThread1.run(TestDeadLock.java:29) Found 1 deadlock.
從打印的線程堆棧中能看到Found one Java-level deadlock,即若是存在線程死鎖狀況,虛擬機在輸出的堆棧中會直接給出死鎖的分析結果。
死鎖的兩個或多個線程是不消耗CPU的,魚人認爲CPU100%的使用率是線程死鎖致使的,這個說法是徹底錯誤的,無限循環(死循環),而且在循環中代碼都是CPU密集型,纔有可能致使CPU的100%使用率,像socket或數據庫等IO操做消耗的CPU很是小。
具體步驟以下:
(1)獲取第一次堆棧信息
(2)等待必定的時間,獲取第二次杜子涵信息
(3)預處理兩次堆棧信息,去掉處於sleeping或waiting狀態的線程,由於這種線程是不消耗CPU的
(4)比較第一次堆棧和第二次堆棧預處理的線程,找出這段時間一直活躍的線程,若是兩次堆棧張同一個線程處於一樣的調用上下文,那麼久應該列爲重點懷疑對象。
若是經過堆棧定位,沒有發現熱點代碼段,那麼CPU太高多是不恰當的內存設置致使的頻繁GC,從而致使CPU太高。
若是系統進入死循環,假設死循環中的代碼都是CPU密集型的,那麼在單核的機器上CPU的使用率應該爲100%,在多核機器上應該有一個CPU核的使用率100%。
以下緣由均可能形成CPU使用率異常:
(1)Java代碼中存在死循環致使CPU太高
(2)系統存在不恰當的設計,儘管沒有死循環,但仍然CPU太高,好比無間斷的輪循
(3)JNI中有死循環代碼
(4)對內存設置過小形成的頻繁GC(堆過小或內存泄漏)
(5)32位JDK下,對內存設置太大形成的頻繁GC
(6)JDK自身存在死循環的Bug
這裏所說的資源包括數據庫鏈接等。大多時候資源不足和性能瓶頸是同一類問題。當資源不足,就會致使資源爭用,請求該資源的線程會被阻塞或掛起,天然就致使性能降低。
對於資源不足的致使的性能瓶頸,打印出的線程堆棧有以下特色:大量的線程停在一樣的調用上下文。
致使資源不足的緣由可能:(1)資源數配置太少(2)得到資源的線程把持資源時間過久,致使資源不足(3)資源用完後,在某種狀況下,沒有關閉或回池,致使可用資源泄露或減小。
致使系統掛死的緣由有不少,其中有一個最多見的緣由是線程掛死。
具體致使線程沒法退出的緣由有不少:
(1)線程正在執行死循環
(2)資源不足或資源泄露,形成當前線程阻塞在鎖對象上(即wait在鎖對象上)長期得不到喚醒notify
(3)若是當前程序和外部通訊,當外部程序掛起無返回時,也會致使當前線程掛起。
線程堆棧定位問題,只能定位子在當前線程上留下痕跡的問題,但線程堆棧對於不留痕跡的問題,就無能爲力。
性能提升,須要且僅須要解決當前的受限資源,當前受限資源多是:
(1)CPU,若是當前CPU已經可以接近100%的利用率,而且代碼業務邏輯沒法再簡化,那麼說明該系統已經達到了性能最大化,若是再想提升性能,只能增長處理器。
(2)其餘資源,如數據庫鏈接數量等,若是CPU利用率老是沒法接近100%,那麼經過修改代碼儘可能提升CPU的使用率,那麼總體性能也會得到極大提升。
性能調優的終極目標是:逐漸增大壓力,系統的CPU利用率可以接近100%。
若是系統具備以下特色,說明這個系統存在性能瓶頸:
(1)隨着系統逐步增長壓力,可是CPU的使用率沒法趨近於100%,儘管還有可用的CPU,可是壓力楞是壓不上
(2)持續運行緩慢,時常發現應用程序運行緩慢,經過改變環境因子如負載量、數據庫鏈接等,也沒法有效提高總體響應時間
(3)系統性能隨時間的增長逐漸降低,在負載穩定的狀況下,系統運行時間越長速度越慢
(1)不相關的兩個函數,共用了一個鎖,或不一樣的共享變量共用了同一個鎖,無謂地製造出了資源爭用。
只有訪問共享變量的代碼段才須要使用鎖保護,並且每個共享變量對應一個本身的鎖,而不要讓全部的共享變量使用同一把鎖。同時,若是可能儘可能避免將synchronized加在整個方法上面,而是將synchronized加在儘可能少的代碼上。
(2)鎖的粒度過大
這種設計確定可使用notify和wait來完成一樣的功能。
每次+操做都會產生一個臨時對象,並伴隨着數據拷貝,這個對性能是一個極大的消耗。
在多線程場景下,若是線程模型不恰當,也會使性能低下。
內存泄漏會致使GC愈來愈頻繁,而GC操做死CPU密集型操做,頻繁GC會致使系統總體性能嚴重降低。
總的原則是在系統中算法已經足夠簡化,即從算法的角度沒法提高性能時,當增長壓力時,CPU上升,隨着壓力的增長,CPU的使用率能趨向於100%,此時說明系統的性能已經榨乾,即系統知足以下兩個條件,那麼性能調優即結束:(1)算法足夠優化(2)沒有線程/資源的使用不當而致使的CPU利用不足