Thread Dump介紹
什么是Thread Dump
Thread Dump是非常有用的診斷Java應用問題的工具瞧栗。每一個Java虛擬機都有及時生成所有線程在某一點狀態(tài)的thread-dump的能力场仲,雖然各個 Java虛擬機打印的thread dump略有不同蝠猬,但是 大多都提供了當前活動線程的快照,及JVM中所有Java線程的堆棧跟蹤信息,堆棧信息一般包含完整的類名及所執(zhí)行的方法苫幢,如果可能的話還有源代碼的行數。
Thread Dump特點
- 能在各種操作系統(tǒng)下使用垫挨;
- 能在各種Java應用服務器下使用韩肝;
- 能在生產環(huán)境下使用而不影響系統(tǒng)的性能;
- 能將問題直接定位到應用程序的代碼行上九榔;
Thread Dump抓取
一般當服務器掛起哀峻,崩潰或者性能低下時,就需要抓取服務器的線程堆棧(Thread Dump)用于后續(xù)的分析哲泊。在實際運行中剩蟀,往往一次 dump的信息,還不足以確認問題切威。為了反映線程狀態(tài)的動態(tài)變化育特,需要接連多次做thread dump,每次間隔10-20s先朦,建議至少產生三次 dump信息缰冤,如果每次 dump都指向同一個問題,我們才確定問題的典型性喳魏。
- 操作系統(tǒng)命令獲取ThreadDump
ps –ef | grep java
kill -3
注意:
一定要謹慎, 一步不慎就可能讓服務器進程被殺死棉浸。kill -9 命令會殺死進程。
- JVM 自帶的工具獲取線程堆棧
jps 或 ps –ef | grep java (獲取PID)
jstack [-l ] | tee -a jstack.log(獲取ThreadDump)
Thread Dump分析
Thread Dump信息
- 頭部信息:時間刺彩,JVM信息
2019-10-28 19:05:06
Full thread dump Java HotSpot(TM) Server VM (16.3-b01 mixed mode):
- 線程INFO信息塊:
1. "Timer-0" daemon prio=10 tid=0xac190c00 nid=0xaef in Object.wait() [0xae77d000]
# 線程名稱:Timer-0迷郑;線程類型:daemon;優(yōu)先級: 10创倔,默認是5嗡害;
# JVM線程id:tid=0xac190c00,JVM內部線程的唯一標識(通過java.lang.Thread.getId()獲取畦攘,通常用自增方式實現)霸妹。
# 對應系統(tǒng)線程id(NativeThread ID):nid=0xaef,和top命令查看的線程pid對應念搬,不過一個是10進制抑堡,一個是16進制。(通過命令:top -H -p pid朗徊,可以查看該進程的所有線程信息)
# 線程狀態(tài):in Object.wait()首妖;
# 起始棧地址:[0xae77d000],對象的內存地址爷恳,通過JVM內存查看工具有缆,能夠看出線程是在哪兒個對象上等待;
2. java.lang.Thread.State: TIMED_WAITING (on object monitor)
3. at java.lang.Object.wait(Native Method)
4. -waiting on <0xb3885f60> (a java.util.TaskQueue) # 繼續(xù)wait
5. at java.util.TimerThread.mainLoop(Timer.java:509)
6. -locked <0xb3885f60> (a java.util.TaskQueue) # 已經locked
7. at java.util.TimerThread.run(Timer.java:462)
Java thread statck trace:是上面2-7行的信息。 到目前為止這是最重要的數據棚壁,Java stack trace提供了大部分信息來精確定位問題根源杯矩。
-
Java thread statck trace詳解:
堆棧信息應該逆向解讀: 程序先執(zhí)行的是第7行,然后是第6行袖外,依次類推史隆。
- locked <0xb3885f60> (a java.util.ArrayList)
- waiting on <0xb3885f60> (a java.util.ArrayList)
也就是說對象先上鎖,鎖住對象0xb3885f60曼验,然后釋放該對象鎖泌射,進入waiting狀態(tài)。 為啥會出現這樣的情況呢鬓照?看看下面的java代碼示例熔酷,就會明白:
synchronized(obj) {
.........
obj.wait();
.........
}
如上,線程的執(zhí)行過程豺裆,先用 synchronized 獲得了這個對象的 Monitor(對應于 locked <0xb3885f60> )拒秘。當執(zhí)行到 obj.wait(),線程即放棄了 Monitor的所有權臭猜,進入 “wait set”隊列(對應于 waiting on <0xb3885f60> )躺酒。
在堆棧的第一行信息中,進一步標明了線程在代碼級的狀態(tài)获讳, 例如:
java.lang.Thread.State: TIMED_WAITING (parking)
解釋如下:
|blocked|
> This thread tried to enter asynchronized block, but the lock was taken by another thread. This thread isblocked until the lock gets released.
|blocked (on thin lock)|
> This is the same state asblocked, but the lock in question is a thin lock.
|waiting|
> This thread calledObject.wait() on an object. The thread will remain there until some otherthread sends a notification to that object.
|sleeping|
> This thread calledjava.lang.Thread.sleep().
|parked|
> This thread calledjava.util.concurrent.locks.LockSupport.park().
|suspended|
> The thread's execution wassuspended by java.lang.Thread.suspend() or a JVMTI agent call.
Thread狀態(tài)分析
線程的狀態(tài)是一個很重要的東西阴颖,因此thread dump中會顯示這些狀態(tài)活喊,通過對這些狀態(tài)的分析丐膝,能夠得出線程的運行狀況,進而發(fā)現可能存在的問題钾菊。線程的狀態(tài)在Thread.State這個枚舉類型中定義:
public enum State
{
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
-
NEW:
每一個線程帅矗,在堆內存中都有一個對應的Thread對象。 Thread t = new Thread();當剛剛在堆內存中創(chuàng)建Thread對象煞烫,還沒有調用t.start()方法之前浑此,線程就處在NEW狀態(tài)。在這個狀態(tài)上滞详,線程與普通的java對象沒有什么區(qū)別凛俱,就僅僅是一個堆內存中的對象。
-
RUNNABLE:
該狀態(tài)表示線程具備所有運行條件料饥,在運行隊列中準備操作系統(tǒng)的調度蒲犬,或者正在運行。 這個狀態(tài)的線程比較正常岸啡,但如果線程長時間停留在在這個狀態(tài)就不正常了原叮,這說明線程運行的時間很長(存在性能問題),或者是線程一直得不得執(zhí)行的機會(存在線程饑餓的問題)。
-
BLOCKED:
線程正在等待獲取java對象的監(jiān)視器(也叫內置鎖)奋隶,即線程正在等待進入由synchronized
保護的方法或者代碼塊擂送。 synchronized用來保證原子性,任意時刻最多只能由一個線程進入該臨界區(qū)域唯欣,其他線程只能排隊等待嘹吨。
-
WAITING:
處在該線程的狀態(tài),正在等待某個事件的發(fā)生境氢,只有特定的條件滿足躺苦,才能獲得執(zhí)行機會。而產生這個特定的事件产还,通常都是另一個線程匹厘。也就是說,如果不發(fā)生特定的事件脐区,那么處在該狀態(tài)的線程一直等待愈诚,不能獲取執(zhí)行的機會。 比如:
A線程調用了obj對象的
obj.wait()
方法牛隅,如果沒有線程調用obj.notify
或obj.notifyAll
炕柔,那么A線程就沒有辦法恢復運行;
如果A線程調用了LockSupport.park()
媒佣,沒有別的線程調用LockSupport.unpark(A)
匕累,那么A沒有辦法恢復運行。
-
TIMED_WAITING:
J.U.C中很多與線程相關類默伍,都提供了限時版本和不限時版本的API欢嘿。TIMED_WAITING意味著線程調用了限時版本的API,正在等待時間流逝也糊。 當等待時間過去后炼蹦,線程一樣可以恢復運行。如果線程進入了WAITING狀態(tài)狸剃,一定要特定的事件發(fā)生才能恢復運行掐隐;而處在TIMED_WAITING的線程,如果特定的事件發(fā)生或者是時間流逝完畢钞馁,都會恢復運行虑省。
-
TERMINATED:
線程執(zhí)行完畢,執(zhí)行完run方法正常返回僧凰,或者拋出了運行時異常而結束探颈,線程都會停留在這個狀態(tài)。 這個時候線程只剩下Thread對象了允悦,沒有什么用了膝擂。
關鍵狀態(tài)分析
-
Wait on condition:
The thread is either sleeping or waiting to be notified by another thread. 該狀態(tài)說明它在等待另一個條件的發(fā)生虑啤,來把自己喚醒,或者干脆它是調用了 sleep(n)架馋。
此時線程狀態(tài)大致為以下幾種:
- java.lang.Thread.State: WAITING (parking):一直等那個條件發(fā)生狞山;
- java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定時的,那個條件不到來叉寂,也將定時喚醒自己萍启。
-
Waiting for Monitor Entry 和 in Object.wait():
The thread is waiting to get the lock for an object (some other thread may be holding the lock). This happens if two or more threads try to execute synchronized code. Note that the lock is always for an object and not for individual methods.
在多線程的JAVA程序中,實現線程之間的同步屏鳍,就要說說 Monitor勘纯。 Monitor是Java中用以實現線程之間的互斥與協(xié)作的主要手段,它可以看成是對象或者Class的鎖钓瞭。 每一個對象都有驳遵,也僅有一個 Monitor。下面這個圖山涡,描述了線程和 Monitor之間關系堤结,以及線程的狀態(tài)轉換圖:
如上圖,每個Monitor在某個時刻鸭丛,只能被一個線程擁有竞穷,該線程就是 “ActiveThread”,而其它線程都是 “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) {
.........
}
這時有兩種可能性:
- 該 monitor不被其它線程擁有菜皂, Entry Set里面也沒有其它等待線程贞绵。本線程即成為相應類或者對象的 Monitor的
- Owner荠卷,執(zhí)行臨界區(qū)的代碼姑宽。 該 monitor被其它線程擁有懒棉,本線程在 Entry Set隊列中等待吸重。
在第一種情況下你画,線程將處于 “Runnable”的狀態(tài)沟娱,而第二種情況下械巡,線程 DUMP會顯示處于 “waiting for monitor entry”七婴。 如下:
"Thread-0" prio=10 tid=0x08222eb0 nid=0x9 waiting for monitor entry [0xf927b000..0xf927bdb8]
at testthread.WaitThread.run(WaitThread.java:39)
- waiting to lock <0xef63bf08> (a java.lang.Object)
- locked <0xef63beb8> (a java.util.ArrayList)
at java.lang.Thread.run(Thread.java:595)
臨界區(qū)的設置乳怎,是為了保證其內部的代碼執(zhí)行的原子性和完整性彩郊。 但是因為臨界區(qū)在任何時間只允許線程串行通過,這和我們多線程的程序的初衷是相反的。如果在多線程的程序中秫逝,大量使用 synchronized恕出,或者不適當的使用了它,會造成大量線程在臨界區(qū)的入口等待违帆,造成系統(tǒng)的性能大幅下降浙巫。 如果在線程 DUMP中發(fā)現了這個情況,應該審查源碼刷后,改進程序的畴。
再看“Wait Set”里面的線程。 當線程獲得了 Monitor尝胆,進入了臨界區(qū)之后丧裁,如果發(fā)現線程繼續(xù)運行的條件沒有滿足,它則調用對象(一般就是被 synchronized 的對象)的 wait() 方法含衔,放棄 Monitor渣慕,進入 “Wait Set”隊列。只有當別的線程在該對象上調用了 notify() 或者 notifyAll()抱慌,“Wait Set”隊列中線程才得到機會去競爭逊桦, 但是只有一個線程獲得對象的Monitor,恢復到運行態(tài)抑进。在 “Wait Set”中的線程强经, DUMP中表現為: in Object.wait()。 如下:
"Thread-1" prio=10 tid=0x08223250 nid=0xa in Object.wait() [0xef47a000..0xef47aa38]
at java.lang.Object.wait(Native Method)
- waiting on <0xef63beb8> (a java.util.ArrayList)
at java.lang.Object.wait(Object.java:474)
at testthread.MyWaitThread.run(MyWaitThread.java:40)
- locked <0xef63beb8> (a java.util.ArrayList)
at java.lang.Thread.run(Thread.java:595)
綜上寺渗,一般CPU很忙時匿情,則關注runnable的線程,CPU很閑時信殊,則關注waiting for monitor entry的線程炬称。
-
JDK 5.0 的 Lock
上面提到如果 synchronized和 monitor機制運用不當,可能會造成多線程程序的性能問題涡拘。在 JDK 5.0中玲躯,引入了 Lock機制,從而使開發(fā)者能更靈活的開發(fā)高性能的并發(fā)多線程程序鳄乏,可以替代以往 JDK中的 synchronized和 Monitor的 機制跷车。但是,要注意的是橱野,因為 Lock類只是一個普通類朽缴,JVM無從得知 Lock對象的占用情況,所以在線程 DUMP中水援,也不會包含關于 Lock的信息密强, 關于死鎖等問題茅郎,就不如用 synchronized的編程方式容易識別。
關鍵狀態(tài)示例
- 顯示BLOCKED狀態(tài)
public class BlockedState
{
private static Object object = new Object();
public static void main(String[] args)
{
Runnable task = new Runnable() {
@Override
public void run()
{
synchronized (object)
{
long begin = System.currentTimeMillis();
long end = System.currentTimeMillis();
// 讓線程運行5分鐘,會一直持有object的監(jiān)視器
while ((end - begin) <= 5 * 60 * 1000)
{
}
}
}
};
new Thread(task, "t1").start();
new Thread(task, "t2").start();
}
}
先獲取object的線程會執(zhí)行5分鐘或渤,這5分鐘內會一直持有object的監(jiān)視器只洒,另一個線程無法執(zhí)行處在BLOCKED狀態(tài):
Full thread dump Java HotSpot(TM) Server VM (20.12-b01 mixed mode):
"DestroyJavaVM" prio=6 tid=0x00856c00 nid=0x1314 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
"t2" prio=6 tid=0x27d7a800 nid=0x1350 waiting for monitor entry [0x2833f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at jstack.BlockedState$1.run(BlockedState.java:17)
- waiting to lock <0x1cfcdc00> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:662)
"t1" prio=6 tid=0x27d79400 nid=0x1338 runnable [0x282ef000]
java.lang.Thread.State: RUNNABLE
at jstack.BlockedState$1.run(BlockedState.java:22)
- locked <0x1cfcdc00> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:662)
通過thread dump可以看到:t2線程確實處在BLOCKED (on object monitor)。waiting for monitor entry 等待進入synchronized保護的區(qū)域劳坑。
- 顯示WAITING狀態(tài)
public class WaitingState
{
private static Object object = new Object();
public static void main(String[] args)
{
Runnable task = new Runnable() {
@Override
public void run()
{
synchronized (object)
{
long begin = System.currentTimeMillis();
long end = System.currentTimeMillis();
// 讓線程運行5分鐘,會一直持有object的監(jiān)視器
while ((end - begin) <= 5 * 60 * 1000)
{
try
{
// 進入等待的同時,會進入釋放監(jiān)視器
object.wait();
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
};
new Thread(task, "t1").start();
new Thread(task, "t2").start();
}
}
Full thread dump Java HotSpot(TM) Server VM (20.12-b01 mixed mode):
"DestroyJavaVM" prio=6 tid=0x00856c00 nid=0x1734 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
"t2" prio=6 tid=0x27d7e000 nid=0x17f4 in Object.wait() [0x2833f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x1cfcdc00> (a java.lang.Object)
at java.lang.Object.wait(Object.java:485)
at jstack.WaitingState$1.run(WaitingState.java:26)
- locked <0x1cfcdc00> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:662)
"t1" prio=6 tid=0x27d7d400 nid=0x17f0 in Object.wait() [0x282ef000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x1cfcdc00> (a java.lang.Object)
at java.lang.Object.wait(Object.java:485)
at jstack.WaitingState$1.run(WaitingState.java:26)
- locked <0x1cfcdc00> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:662)
可以發(fā)現t1和t2都處在WAITING (on object monitor)毕谴,進入等待狀態(tài)的原因是調用了in Object.wait()。通過J.U.C包下的鎖和條件隊列距芬,也是這個效果涝开, 大家可以自己實踐下。
- 顯示TIMED_WAITING狀態(tài)
package jstack;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TimedWaitingState
{
// java的顯示鎖,類似java對象內置的監(jiān)視器
private static Lock lock = new ReentrantLock();
// 鎖關聯(lián)的條件隊列(類似于object.wait)
private static Condition condition = lock.newCondition();
public static void main(String[] args)
{
Runnable task = new Runnable() {
@Override
public void run()
{
// 加鎖,進入臨界區(qū)
lock.lock();
try
{
condition.await(5, TimeUnit.MINUTES);
} catch (InterruptedException e)
{
e.printStackTrace();
}
// 解鎖,退出臨界區(qū)
lock.unlock();
}
};
new Thread(task, "t1").start();
new Thread(task, "t2").start();
}
}
Full thread dump Java HotSpot(TM) Server VM (20.12-b01 mixed mode):
"DestroyJavaVM" prio=6 tid=0x00856c00 nid=0x169c waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
"t2" prio=6 tid=0x27d7d800 nid=0xc30 waiting on condition [0x2833f000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x1cfce5b8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:196)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2116)
at jstack.TimedWaitingState$1.run(TimedWaitingState.java:28)
at java.lang.Thread.run(Thread.java:662)
"t1" prio=6 tid=0x280d0c00 nid=0x16e0 waiting on condition [0x282ef000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x1cfce5b8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:196)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2116)
at jstack.TimedWaitingState$1.run(TimedWaitingState.java:28)
at java.lang.Thread.run(Thread.java:662)
可以看到t1和t2線程都處在java.lang.Thread.State: TIMED_WAITING (parking)框仔,這個parking代表是調用的JUC下的工具類舀武,而不是java默認的監(jiān)視器。
案例分析
問題場景
- CPU飆高离斩,load高银舱,響應很慢
- 一個請求過程中多次dump;
- 對比多次dump文件的runnable線程跛梗,如果執(zhí)行的方法有比較大變化寻馏,說明比較正常。如果在執(zhí)行同一個方法核偿,就有一些問題了诚欠;
- 查找占用CPU最多的線程
- 使用命令:top -H -p pid(pid為被測系統(tǒng)的進程號),找到導致CPU高的線程ID漾岳,對應thread dump信息中線程的nid 轰绵,只不過一個是十進制,一個是十六進制尼荆; 在thread
- dump中左腔,根據top命令查找的線程id,查找對應的線程堆棧信息捅儒;
- CPU使用率不高但是響應很慢
進行dump液样,查看是否有很多thread struck在了i/o、數據庫等地方 野芒,定位瓶頸原因蓄愁;
- 請求無法響應
多次dump,對比是否所有的runnable線程都一直在執(zhí)行相同的方法狞悲, 如果是的,恭喜你妇斤,鎖住了摇锋!
死鎖
死鎖經常表現為程序的停頓丹拯,或者不再響應用戶的請求。從操作系統(tǒng)上觀察荸恕,對應進程的CPU占用率為零乖酬,很快會從top或prstat的輸出中消失。
比如在下面這個示例中融求,是個較為典型的死鎖情況:
"Thread-1" prio=5 tid=0x00acc490 nid=0xe50 waiting for monitor entry [0x02d3f000
..0x02d3fd68]
at deadlockthreads.TestThread.run(TestThread.java:31)
- waiting to lock <0x22c19f18> (a java.lang.Object)
- locked <0x22c19f20> (a java.lang.Object)
"Thread-0" prio=5 tid=0x00accdb0 nid=0xdec waiting for monitor entry [0x02cff000
..0x02cff9e8]
at deadlockthreads.TestThread.run(TestThread.java:31)
- waiting to lock <0x22c19f20> (a java.lang.Object)
- locked <0x22c19f18> (a java.lang.Object)
在 JAVA 5中加強了對死鎖的檢測咬像。線程 Dump中可以直接報告出 Java級別的死鎖, 如下所示:
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x0003f334 (object 0x22c19f18, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x0003f314 (object 0x22c19f20, a java.lang.Object),
which is held by "Thread-1"
熱鎖
熱鎖生宛,也往往是導致系統(tǒng)性能瓶頸的主要因素县昂。其表現特征為:由于多個線程對臨界區(qū),或者鎖的競爭陷舅, 可能出現:
- 頻繁的線程的上下文切換: 從操作系統(tǒng)對線程的調度來看倒彰,當線程在等待資源而阻塞的時候,操作系統(tǒng)會將之切換出來莱睁,放到等待的隊列待讳,當線程獲得資源之后,調度算法會將這個線程切換進去仰剿,放到執(zhí)行隊列中创淡。
- 大量的系統(tǒng)調用: 因為線程的上下文切換,以及熱鎖的競爭南吮,或者臨界區(qū)的頻繁的進出辩昆,都可能導致大量的系統(tǒng)調用。
- 大部分CPU開銷用在“系統(tǒng)態(tài)”: 線程上下文切換旨袒,和系統(tǒng)調用汁针,都會導致 CPU在 “系統(tǒng)態(tài) ”運行,換而言之砚尽,雖然系統(tǒng)很忙碌施无,但是CPU用在 “用戶態(tài) ”的比例較小,應用程序得不到充分的 CPU資源必孤。
- 隨著CPU數目的增多猾骡,系統(tǒng)的性能反而下降。 因為CPU數目多敷搪,同時運行的線程就越多兴想,可能就會造成更頻繁的線程上下文切換和系統(tǒng)態(tài)的CPU開銷,從而導致更糟糕的性能赡勘。
上面的描述嫂便,都是一個 scalability(可擴展性)很差的系統(tǒng)的表現。從整體的性能指標看闸与,由于線程熱鎖的存在毙替,程序的響應時間會變長岸售,吞吐量會降低。
那么厂画,怎么去了解 “熱鎖 ”出現在什么地方呢凸丸?
一個重要的方法是 結合操作系統(tǒng)的各種工具觀察系統(tǒng)資源使用狀況,以及收集Java線程的DUMP信息袱院,看線程都阻塞在什么方法上屎慢, 了解原因,才能找到對應的解決方法忽洛。
JVM重要線程
JVM運行過程中產生的一些比較重要的線程羅列如下:
線程名稱 | 所屬 | 解釋說明 |
---|---|---|
Attach Listener | JVM |
Attach Listener 線程是負責接收到外部的命令腻惠,而對該命令進行執(zhí)行的并把結果返回給發(fā)送者。通常我們會用一些命令去要求JVM給我們一些反饋信息脐瑰,如:java -version妖枚、jmap、jstack 等等苍在。 如果該線程在JVM啟動的時候沒有初始化绝页,那么,則會在用戶第一次執(zhí)行JVM命令時寂恬,得到啟動续誉。 |
Signal Dispatcher | JVM | 前面提到Attach Listener 線程的職責是接收外部JVM命令,當命令接收成功后初肉,會交給signal dispather 線程去進行分發(fā)到各個不同的模塊處理命令酷鸦,并且返回處理結果。signal dispather 線程也是在第一次接收外部JVM命令時牙咏,進行初始化工作臼隔。 |
CompilerThread0 | JVM | 用來調用JITing ,實時編譯裝卸class 妄壶。 通常摔握,JVM會啟動多個線程來處理這部分工作,線程名稱后面的數字也會累加丁寄,例如:CompilerThread1 氨淌。 |
Concurrent Mark-Sweep GC Thread | JVM | 并發(fā)標記清除垃圾回收器(就是通常所說的CMS GC)線程, 該線程主要針對于老年代垃圾回收伊磺。ps:啟用該垃圾回收器盛正,需要在JVM啟動參數中加上:-XX:+UseConcMarkSweepGC 。 |
DestroyJavaVM | JVM | 執(zhí)行main() 的線程屑埋,在main執(zhí)行完后調用JNI中的 jni_DestroyJavaVM() 方法喚起DestroyJavaVM 線程豪筝,處于等待狀態(tài),等待其它線程(Java線程和Native線程)退出時通知它卸載JVM。每個線程退出時壤蚜,都會判斷自己當前是否是整個JVM中最后一個非deamon 線程即寡,如果是徊哑,則通知DestroyJavaVM 線程卸載JVM袜刷。 |
Finalizer Thread | JVM | 這個線程也是在main線程之后創(chuàng)建的,其優(yōu)先級為10莺丑,主要用于在垃圾收集前著蟹,調用對象的finalize() 方法;關于Finalizer線程的幾點:1) 只有當開始一輪垃圾收集時梢莽,才會開始調用finalize() 方法萧豆;因此并不是所有對象的finalize() 方法都會被執(zhí)行;2) 該線程也是daemon 線程昏名,因此如果虛擬機中沒有其他非daemon 線程涮雷,不管該線程有沒有執(zhí)行完finalize() 方法,JVM也會退出轻局;3) JVM在垃圾收集時會將失去引用的對象包裝成Finalizer 對象(Reference 的實現)洪鸭,并放入ReferenceQueue ,由Finalizer 線程來處理仑扑;最后將該Finalizer對象的引用置為null览爵,由垃圾收集器來回收;4) JVM為什么要單獨用一個線程來執(zhí)行finalize() 方法呢镇饮?如果JVM的垃圾收集線程自己來做蜓竹,很有可能由于在finalize() 方法中誤操作導致GC線程停止或不可控,這對GC線程來說是一種災難储藐; |
Low Memory Detector | JVM | 這個線程是負責對可使用內存進行檢測俱济,如果發(fā)現可用內存低,分配新的內存空間钙勃。 |
Reference Handler | JVM | JVM在創(chuàng)建main 線程后就創(chuàng)建Reference Handler 線程蛛碌,其優(yōu)先級最高,為10肺缕,它主要用于處理引用對象本身(軟引用左医、弱引用、虛引用)的垃圾回收問題 同木。 |
VM Thread | JVM | 這個線程就比較牛b了浮梢,是JVM里面的線程母體,根據hotspot源碼(vmThread.hpp )里面的注釋彤路,它是一個單個的對象(最原始的線程)會產生或觸發(fā)所有其他的線程秕硝,這個單個的VM線程是會被其他線程所使用來做一些VM操作(如:清掃垃圾等) |