CPU經常會成為系統性能的瓶頸,可能:
- 記憶體泄露導致頻繁GC,進而引起CPU使用率過高
- 代碼Bug創建了大量的執行緒,導致CPU頻繁背景關系切換
通常所說的CPU使用率過高,隱含著一個用來比較高與低的基準值,比如
- JVM在峰值負載下的平均CPU利用率40%
- CPU使用率飆到80%就可認為不正常
JVM行程包含多個Java執行緒:
- 一些在等待作業
- 另一些則正在執行任務
最重要的是找到哪些執行緒在消耗CPU,通過執行緒堆疊定位到問題代碼
如果沒有找到個別執行緒的CPU使用率特別高,考慮是否執行緒背景關系切換導致了CPU使用率過高,
案例
程式模擬CPU使用率過高 - 在執行緒池中創建4096個執行緒
在Linux環境下啟動程式:
java -Xss256k -jar demo-0.0.1-SNAPSHOT.jar
執行緒堆疊大小指定為256KB,對于測驗程式來說,作業系統默認值8192KB過大,因為需要創建4096個執行緒,
使用top命令,我們看到Java行程的CPU使用率達到了961.6%,注意到行程ID是55790,

用更精細化的top命令查看這個Java行程中各執行緒使用CPU的情況:
#top -H -p 55790

可見,有個叫“scheduling-1”的執行緒占用了較多的CPU,達到了42.5%,因此下一步我們要找出這個執行緒在做什么事情,
- 為了找出執行緒在做什么,用jstack生成執行緒快照,
jstack輸出較大,一般將其寫入檔案:
jstack 55790 > 55790.log
打開55790.log,定位到第4步中找到的名為 scheduling-1 的執行緒,其執行緒堆疊:

看到AbstractExecutorService#submit這個函式呼叫,說明它是Spring Boot啟動的周期性任務執行緒,向執行緒池中提交任務,該執行緒消耗了大量CPU,
背景關系切換開銷?
經歷上述程序,往往已經可以定位到大量消耗CPU的執行緒及bug代碼,比如死回圈,但對于該案例:Java行程占用的CPU是961.6%, 而“scheduling-1”執行緒只占用了42.5%的CPU,那其它CPU被誰占用了?
第4步用top -H -p pid命令看到的執行緒串列中還有許多名為“pool-1-thread-x”的執行緒,它們單個的CPU使用率不高,但是似乎數量比較多,你可能已經猜到,這些就是執行緒池中干活的執行緒,那剩下的CPU是不是被這些執行緒消耗了呢?
還需要看jstack的輸出結果,主要是看這些執行緒池中的執行緒是不是真的在干活,還是在“休息”呢?
發現這些“pool-1-thread-x”執行緒基本都處WAITING狀態,

- Blocking指的是一個執行緒因為等待臨界區的鎖(Lock或者synchronized關鍵字)而被阻塞的狀態,請你注意的是處于這個狀態的執行緒還沒有拿到鎖
- Waiting指的是一個執行緒拿到了鎖,但需等待其他執行緒執行某些操作,比如呼叫了Object.wait、Thread.join或LockSupport.park方法時,進入Waiting狀態,前提是這個執行緒已經拿到鎖了,并且在進入Waiting狀態前,os層面會自動釋放鎖,當等待條件滿足,外部呼叫了Object.notify或者LockSupport.unpark方法,執行緒會重新競爭鎖,成功獲得鎖后才能進入到Runnable狀態繼續執行,
回到我們的“pool-1-thread-x”執行緒,這些執行緒都處在“Waiting”狀態,從執行緒堆疊我們看到,這些執行緒“等待”在getTask方法呼叫上,執行緒嘗試從執行緒池的佇列中取任務,但是佇列為空,所以通過LockSupport.park呼叫進到了“Waiting”狀態,那“pool-1-thread-x”執行緒有多少個呢?通過下面這個命令來統計一下,結果是4096,正好跟執行緒池中的執行緒數相等,
grep -o 'pool-2-thread' 55790.log | wc -l

剩下CPU到底被誰消耗了?
應該懷疑CPU的背景關系切換開銷了,因為我們看到Java行程中的執行緒數比較多,
下面通過vmstat命令來查看一下作業系統層面的執行緒背景關系切換活動:

cs那一欄表示執行緒背景關系切換次數,in表示CPU中斷次數,我們發現這兩個數字非常高,基本證實了我們的猜測,執行緒背景關系切切換消耗了大量CPU,
那具體是哪個行程導致的呢?
停止Spring Boot程式,再次運行vmstat命令,會看到in和cs都大幅下降,這就證實引起執行緒背景關系切換開銷的Java行程正是55790,

總結
遇到CPU過高,首先定位哪個行程導致的,之后可以通過top -H -p pid命令定位到具體的執行緒,
其次還要通jstack查看執行緒的狀態,看看執行緒的個數或者執行緒的狀態,如果執行緒數過多,可以懷疑是執行緒背景關系切換的開銷,我們可以通過vmstat和pidstat這兩個工具進行確認,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/291853.html
標籤:java
上一篇:SpringBoot跳轉頁面+thymeleaf,轉發和重定向的區別及應用場景,不同域名通過iframe嵌套顯示提示拒絕連接
下一篇:python資料結構之演算法分析
