jstack排查CPU佔用過高之BlockingQueue的陷阱

原創文章, 轉載請私信. 訂閱號 tastejava 學習加思考, 仔細品味java之美

背景

測試服務器部署一個項目, 供其他部門項目聯調使用. 運維給發消息說那臺服務器CPU佔用過高一直在報警, 雖然不會影響其他虛擬機但是會影響物理機, 需要及時處理一下. 在服務器用top命令看了一下, CPU佔用已經高達190%以上, 這臺測試服務器是Intel® Xeon® CPU E7-4820 v2 @ 2.00GHz總共2個核心, 也就是說兩個核心幾乎都100%負載.

什麼是jstack

jstack是JDK在%JAVA_HOME%/bin目錄下提供的一個用於打印Java進程線程信息的工具, 能幫助定位異常線程具體代碼位置.
jstack在排查CPU佔用異常, 死鎖時作用非常大, 這麼一個好用的工具使用起來卻非常簡單, 具體命令格式爲:
jstack 參數 pid
其中pid是要查看線程信息的進程id, 可以通過ps, jps, top等命令獲取. 主要的核心在於jstack的參數, 它的參數只有4個.

①-F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
強制導出線程信息, 當jstack命令沒有被java進程響應時使用(java進程被掛起狀態)
②-m  to print both java and native frames (mixed mode) 打印java和native所有方法的線程信息
③-l  long listing. Prints additional information about locks 打印額外的鎖信息
④-h or -help to print this help message 打印幫助信息

爲什麼說jstack使用簡單呢, 4個參數中, -h打印幫助信息不需要記憶, 只單獨執行jstack就默認打印幫助信息, -m將native方法線程信息一樣打印出來, 平時排查問題一般不需要打印native線程信息. 用的多的就只剩下-F和-l兩個參數了, 只要jstack被java進程正常響應返回結果-F強制打印也用不到. 平時高頻問題的排查(死鎖, CPU佔用異常), 只需要用到 -l 一個參數即可.

用到的命令

排查CPU佔用過高用到的所有命令都總結在下方, 方便理解排查過程.

  1. top 定位CPU佔用過高的進程PID和其中的線程TID
  2. printf 格式化TID到16進製表現形式
  3. jstack 導出java進程線程信息

解決過程

①首先通過top命令確定服務器的確是CPU佔用過高了, 記錄異常進程的PID, 我這次排查pid是4438.
②繼續用top命令觀察異常進程哪些線程佔用CPU過高, 獲得到線程tid, 用到參數-p指定進程pid, -H展示所有線程, 具體命令如下(top查看線程id還是第一列, 標題是pid, 不過實際是tid):

top -H -p 4438

我這次排查佔用CPU異常的線程有兩個,tid分別爲4485和4496.
③利用jstack -l 進程id > jstack.log命令導出線程信息, 保留現場.
④利用printf “%x\n” 線程tid命令將tid轉換爲16進制, 對應4485和4496的16進制分別爲1185, 1190.
⑤在導出的jstack線程信息中搜索16進制的線程tid, 定位異常代碼位置.
通過上面幾個步驟, 終於定位到異常代碼位置, 在我當前的業務裏, 大概場景是啓動了兩個線程持續處理任務, 這兩個Runnable裏都使用了BlockingQueue, 並且爲了提高效率都使用了BlockingQueue的drainTo方法, drainTo方法會把Queue中所有可用數據導出到傳入的集合當中, 但是drainTo不是阻塞方法, 也就是說沒有數據時這兩個線程一直是空循環狀態.消耗了大量CPU.

解決方法

利用BlockingQueue的阻塞方法take來優化方法調用. 具體代碼如下:

Task task = linkedBlockingQueue.take();
List temp = new ArrayList();
linkedBlockingQueue.drainTo(temp);
temp.add(task);

這樣既保留了drainTo批量處理數據的高效, 又讓其擁有了阻塞效果, 沒有數據時方法不會空循環.

總結

如果問題發生在生產環境就比較難排查了, 難保留現場, 難取證, 不過發生在測試環境那就是送分題了.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章