JVM 問題排查

weixin_33782386發表於2016-05-31

CPU使用率高

  • 找出使用率高的程式的pid
top
  • 找出使用率高的執行緒tpid
top -p pid -H
  • 檢視使用率高的執行緒當前在幹什麼
jstack -l pid > stack.log
// 將執行緒的tpid轉為16進位制,到stack.log中查詢
grep tpid stack.log -a3

GC問題

// -t:列印時間戳,1s每隔1秒列印一次
jstat -gcutil -t pid 1s

也可以通過檢視gc日誌來觀察問題

記憶體洩漏

  1. 執行FullGC後不能回收的記憶體不斷增加
  2. 執行jstat -gcutil pid,檢視Old區的使用情況,如果接近100%,則代表記憶體不足。可以先增大記憶體,如果還是不斷增長到溢位,則考慮是否有記憶體洩漏問題
  3. 執行jmap -histo:live pid > memory.log,統計所有存活物件的個數,觀察那些數量最多的物件,特別是自己寫的物件和存放到集合裡沒有釋放的物件
  4. 如果還是無法定位,則執行jmap -dump匯出整個Heap,然後使用工具進行分析,注意看自己寫的類的依賴關係,看看是不是使用完沒有釋放,或者一次性查詢過多的資料導致記憶體溢位

執行緒分析

如果CPU使用率不高,但程式效能低下,則可考慮對執行緒進行分析,看看各個主要的執行緒都在做什麼,是否有鎖爭用或IO阻塞問題

為了方便分析,最好給每個執行緒或者執行緒池命名

jstack -l pid > stack.log

執行緒狀態

狀態 描述
New 執行緒剛被建立,還沒有被執行
Runnable 執行緒正在執行
Blocked 等待其他執行緒釋放鎖
Waiting 呼叫了wait或join方法,無限等待
Timed_Waiting 呼叫了sleep、wait(interval)、join(interval)方法,有限等待

觀察

死鎖

如果JVM發現有死鎖存在,會在日誌中出現Found one Java-level deadlock

Waiting on condition

在等待一個條件的發生,來把自己喚醒,或者呼叫了sleep方法
此時執行緒狀態:
WAITING(parking):一直等待那個條件發生
TIMED_WAITING(parking或sleeping):定時等待,即使條件不發生,時間到了也可以自己喚醒

如果發現大量執行緒處於此狀態,並且從執行緒的堆疊上檢視到是正在執行網路讀寫,這可能是一個網路瓶頸問題或者第三方響應慢的問題

Blocked

執行緒所需要的資源長時間等待卻一直無法獲取,被標識為阻塞狀態,可以理解為等待資源超時的執行緒。執行緒堆疊中一般存在Waiting to Lock

Waiting for monitor entry 和 in Object.wait()

每個 Monitor在某個時刻,只能被一個執行緒擁有,該執行緒就是Active Thread,而其它執行緒都是Waiting Thread,分別在兩個佇列 Entry Set和Wait Set裡面等候。 在Entry Set中等待的執行緒狀態是Waiting for monitor entry,而在Wait Set中等待的執行緒狀態是in Object.wait()。當被呼叫notify或notifyAll時,只有在Wait Set中的執行緒會被喚醒

1389146-07979cc3a20a74ed.png

Waiting for monitor entry:等待進入一個臨界區 ,所以它在Entry Set佇列中等待。此時執行緒狀態一般都是Blocked,如果存在大量執行緒在此狀態,可能是一個全域性鎖阻塞住了大量執行緒。隨著時間流逝,waiting for monitor entry的執行緒越來越多,沒有減少的趨勢,可能意味著某些執行緒在臨界區裡呆的時間太長了,以至於越來越多新執行緒遲遲無法進入臨界區

當執行緒獲得了Monitor,如果發現執行緒繼續執行的條件沒有滿足,它則呼叫物件(一般就是被 synchronized 的物件)的 wait() 方法,放棄了Monitor,進入Wait Set佇列。此時執行緒狀態大致為以下幾種:TIMED_WAITING (on object monitor)和 WAITING (on object monitor)

等待IO

有時候執行緒狀態是Runnable,但卻是在等待IO

"socketReadThread" prio=6 tid=0x0000000006a0d800 nid=0x1b40 runnable
[0x00000000089ef000] java.lang.Thread.State: RUNNABLE
    at java.net.SocketInputStream.socketRead0(Native Method) 

總結

  1. 如果cpu使用率不高,但效能低下,一般都是由鎖或IO阻塞造成,這時要注意檢視狀態為BLOCKED或者Waiting的執行緒,看它們需要等待什麼鎖或者是否出現了死鎖,再考慮如何優化併發
  2. 如果發現有大量的執行緒都在處在 Wait on condition,從執行緒 stack看,正等待網路讀寫,這可能是一個網路瓶頸的徵兆。因為網路阻塞導致執行緒無法執行。一種情況是網路非常忙,幾乎消耗了所有的頻寬,仍然有大量資料等待網路讀寫;另一種情況也可能是網路空閒,但由於路由等問題,導致包無法正常的到達

參考

效能分析之– JAVA Thread Dump 分析綜述
三個例項演示 Java Thread Dump 日誌分析

相關文章