1. 程式人生 > >線上CPU飆升100%問題排查,一篇足矣

線上CPU飆升100%問題排查,一篇足矣

一、引子

對於網際網路公司,線上CPU飆升的問題很常見(例如某個活動開始,流量突然飆升時),按照本文的步驟排查,基本1分鐘即可搞定!特此整理排查方法一篇,供大家參考討論提高。

二、問題復現

線上系統突然執行緩慢,CPU飆升,甚至到100%,以及Full GC次數過多,接著就是各種報警:例如介面超時報警等。此時急需快速線上排查問題。

三、問題排查

不管什麼問題,既然是CPU飆升,肯定是查一下耗CPU的執行緒,然後看看GC。

3.1 核心排查步驟

1.執行“top”命令:檢視所有程序佔系統CPU的排序。極大可能排第一個的就是咱們的java程序(COMMAND列)。PID那一列就是程序號。

2.執行“top -Hp 程序號”命令:檢視java程序下的所有執行緒佔CPU的情況。

3.執行“printf "%x\n 10"命令 :後續檢視執行緒堆疊資訊展示的都是十六進位制,為了找到咱們的執行緒堆疊資訊,咱們需要把執行緒號轉成16進位制。例如,printf "%x\n 10-》列印:a,那麼在jstack中執行緒號就是0xa.

4.執行 “jstack 程序號 | grep 執行緒ID”  查詢某程序下-》執行緒ID(jstack堆疊資訊中的nid)=0xa的執行緒堆疊資訊。如果“"VM Thread" os_prio=0 tid=0x00007f871806e000 nid=0xa runnable”,第一個雙引號圈起來的就是執行緒名,如果是“VM Thread”這就是虛擬機器GC回收執行緒了

5.執行“jstat -gcutil 程序號 統計間隔毫秒 統計次數(預設代表一致統計)”,檢視某程序GC持續變化情況,如果發現返回中FGC很大且一直增大-》確認Full GC! 也可以使用“jmap -heap 程序ID”檢視一下程序的堆內從是不是要溢位了,特別是老年代內從使用情況一般是達到閾值(具體看垃圾回收器和啟動時配置的閾值)就會程序Full GC。

6.執行“jmap -dump:format=b,file=filename 程序ID”,匯出某程序下記憶體heap輸出到檔案中。可以通過eclipse的mat工具檢視記憶體中有哪些物件比較多,飛機票:Eclipse Memory Analyzer(MAT),記憶體洩漏外掛,安裝使用一條龍;

3.2 原因分析

1.記憶體消耗過大,導致Full GC次數過多

執行步驟1-5:

  • 多個執行緒的CPU都超過了100%,通過jstack命令可以看到這些執行緒主要是垃圾回收執行緒-》上一節步驟2
  • 通過jstat命令監控GC情況,可以看到Full GC次數非常多,並且次數在不斷增加。--》上一節步驟5

確定是Full GC,接下來找到具體原因:

  • 生成大量的物件,導致記憶體溢位-》執行步驟6,檢視具體記憶體物件佔用情況。
  • 記憶體佔用不高,但是Full GC次數還是比較多,此時可能是程式碼中手動呼叫 System.gc()導致GC次數過多,這可以通過新增 -XX:+DisableExplicitGC來禁用JVM對顯示GC的響應。

2.程式碼中有大量消耗CPU的操作,導致CPU過高,系統執行緩慢;

執行步驟1-4:在步驟4jstack,可直接定位到程式碼行。例如某些複雜演算法,甚至演算法BUG,無限迴圈遞迴等等。

 

3.由於鎖使用不當,導致死鎖。

執行步驟1-4: 如果有死鎖,會直接提示。關鍵字:deadlock.步驟四,會打印出業務死鎖的位置。

造成死鎖的原因:最典型的就是2個執行緒互相等待對方持有的鎖。

 

4.隨機出現大量執行緒訪問介面緩慢。

程式碼某個位置有阻塞性的操作,導致該功能呼叫整體比較耗時,但出現是比較隨機的;平時消耗的CPU不多,而且佔用的記憶體也不高。

思路:

首先找到該介面,通過壓測工具不斷加大訪問力度,大量執行緒將阻塞於該阻塞點。

執行步驟1-4:

"http-nio-8080-exec-4" #31 daemon prio=5 os_prio=31 tid=0x00007fd08d0fa000 nid=0x6403 waiting on condition [0x00007000033db000]

   java.lang.Thread.State: TIMED_WAITING (sleeping)-》期限等待

    at java.lang.Thread.sleep(Native Method)

    at java.lang.Thread.sleep(Thread.java:340)

    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)

    at com.*.user.controller.UserController.detail(UserController.java:18)-》業務程式碼阻塞點

如上圖,找到業務程式碼阻塞點,這裡業務程式碼使用了TimeUnit.sleep()方法,使執行緒進入了TIMED_WAITING(期限等待)狀態。關於執行緒狀態,不理解的飛機票:Thread類原始碼剖析

 

5.某個執行緒由於某種原因而進入WAITING狀態,此時該功能整體不可用,但是無法復現;

執行步驟1-4:jstack多查詢幾次,每次間隔30秒,對比一直停留在parking 導致的WAITING狀態的執行緒。例如CountDownLatch倒計時器,使得相關想成等待->AQS(AbstractQueuedSynchronizer AQS框架原始碼剖析)->LockSupport.park()。

"Thread-0" #11 prio=5 os_prio=31 tid=0x00007f9de08c7000 nid=0x5603 waiting on condition [0x0000700001f89000]   
java.lang.Thread.State: WAITING (parking) ->無期限等待
at sun.misc.Unsafe.park(Native Method)    
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)    
at com.*.SyncTask.lambda$main$0(SyncTask.java:8)-》業務程式碼阻塞點
at com.*.SyncTask$$Lambda$1/1791741888.run(Unknown Source)    
at java.lang.Thread.run(Thread.java:748)

四、總結

按照3.1節的6個步驟走下來,基本都能找到問題所在。

 

====參考====

https://mp.weixin.qq.com/s/g8KJhOtiBHWb6wNFrCcLVg