首頁>技術>

背景

最近負責的一個專案上線,執行一段時間後發現對應的程序竟然佔用了700%的CPU,導致公司的物理伺服器都不堪重負,頻繁宕機。那麼,針對這類java程序CPU飆升的問題,我們一般要怎麼去定位解決呢?

一、採用top命令定位程序

登入伺服器,執行top命令,檢視CPU佔用情況,找到程序的pid

top1

很容易發現,PID為29706的java程序的CPU飆升到700%多,且一直降不下來,很顯然出現了問題。

二、使用top -Hp命令定位執行緒

使用 top -Hp <pid> 命令(為Java程序的id號)檢視該Java程序內所有執行緒的資源佔用情況(按shft+p按照cpu佔用進行排序,按shift+m按照記憶體佔用進行排序)此處按照cpu排序:

top -Hp 23602

很容易發現,多個執行緒的CPU佔用達到了90%多。我們挑選執行緒號為30309的執行緒繼續分析。

三、使用jstack命令定位程式碼1.執行緒號轉換為16進位制

printf “%x\n” 命令(tid指執行緒的id號)將以上10進位制的執行緒號轉換為16進位制:

printf "%x\n"  30309

轉換後的結果分別為7665,由於匯出的執行緒快照中執行緒的nid是16進位制的,而16進位制以0x開頭,所以對應的16進位制的執行緒號nid為0x7665

2.採用jstack命令匯出執行緒快照

透過使用dk自帶命令jstack獲取該java程序的執行緒快照並輸入到檔案中: jstack -l > ./jstack_result.txt 命令(為Java程序的id號)來獲取執行緒快照結果並輸入到指定檔案。

jstack -l 29706 > ./jstack_result.txt 1
3.根據執行緒號定位具體程式碼

在jstack_result.txt 檔案中根據執行緒好nid搜尋對應的執行緒描述

cat jstack_result.txt |grep -A 100  7665

根據搜尋結果,判斷應該是ImageConverter.run()方法中的程式碼出現問題

PS

這裡也可以直接採用jstack <pid> |grep -A 200 <nid>來定位具體程式碼

$jstack 44529 |grep -A 200 ae24"System Clock" #28 daemon prio=5 os_prio=0 tid=0x00007efc19e8e800 nid=0xae24 waiting on condition [0x00007efbe0d91000]   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.concurrentC.TimeUnit.sleep(TimeUnit.java:386)    at com.*.order.Controller.OrderController.detail(OrderController.java:37)  //業務程式碼阻塞點
四、分析程式碼解決問題

下面是ImageConverter.run()方法中的部分核心程式碼。邏輯說明:在while迴圈中,不斷讀取堵塞佇列dataQueue中的資料,如果資料為空,則執行continue進行下一次迴圈。如果不為空,則透過poll()方法讀取資料,做相關邏輯處理。

//儲存minicap的socket連線返回的資料   (改用訊息佇列儲存讀到的流資料) ,設定阻塞佇列長度,防止出現記憶體溢位//全域性變數private BlockingQueue<byte[]> dataQueue = new LinkedBlockingQueue<byte[]>(100000);//消費執行緒@Overridepublic void run() {    //long start = System.currentTimeMillis();    while (isRunning) {        //分析這裡從LinkedBlockingQueue        if (dataQueue.isEmpty()) {            continue;        }        byte[] buffer = device.getMinicap().dataQueue.poll();       int len = buffer.length;}

初看這段程式碼好像每什麼問題,但是如果dataQueue物件長期為空的話,這裡就會一直空迴圈,導致CPU飆升。那麼如果解決呢?分析LinkedBlockingQueue阻塞佇列的API發現:

//取出佇列中的頭部元素,如果佇列為空則呼叫此方法的執行緒被阻塞等待,直到有元素能被取出,如果等待過程被中斷則丟擲InterruptedExceptionE take() throws InterruptedException;//取出佇列中的頭部元素,如果佇列為空返回nullE poll();

這兩種取值的API,顯然take方法更時候這裡的場景。

程式碼修改為:

while (isRunning) {   /* if (device.getMinicap().dataQueue.isEmpty()) {        continue;    }*/    byte[] buffer = new byte[0];    try {        buffer = device.getMinicap().dataQueue.take();    } catch (InterruptedException e) {        e.printStackTrace();    }……}

重啟專案後,測試發現專案執行穩定,對應專案程序的CPU消耗佔比不到10%。

CPU飆升的常見原因:

1.空迴圈,本文中的問題其實就這個原因導致的。2.在迴圈的程式碼邏輯中,建立大量的新物件導致頻繁GC3.在迴圈的程式碼邏輯中進行大量無意義的計算。簡單來說,遇見CPU飆升的問題,就要仔細檢查相關執行緒程式碼中的迴圈邏輯,比如for,while等。

總結

CPU飆升問題定位的一般步驟是:1.首先透過top指令檢視當前佔用CPU較高的程序PID;2.檢視當前程序消耗資源的執行緒PID: top -Hp PID3.透過print命令將執行緒PID轉為16進位制,根據該16進位制值去列印的堆疊日誌內查詢,檢視該執行緒所駐留的方法位置。4.透過jstack命令,檢視棧資訊,定位到執行緒對應的具體程式碼。5.分析程式碼解決問題。

本文出處:csdn博主--鬥者_2013

21
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Qt編寫地圖綜合應用25-echart動態互動