生產環境 Tomcat 進程 CPU 長期佔用 100%,導致服務無響應。標準排查鏈路:top 定進程 → ps 定線程 → printf 轉十六進制 → jstack 定代碼行。本文記錄完整操作步驟、截圖解讀與代碼級定位技巧。
top
# 按 P 排序 CPU,記下 PID(假設 28802)

# 確認是 Tomcat 啟動的 Java 進程
ps -ef | grep 28802
# 列出該進程所有線程的 CPU 時間、TID
ps -mp 28802 -o THREAD,tid,time

關鍵列:TID(線程 ID,十進制)、TIME(佔用 CPU 時間)。找到 TIME 最大的 TID(如 28802,已運行近 2 小時)。
printf "%x\n" 28802
# 輸出:7082 (十六進制)

原因:jstack 輸出的線程 ID(nid)為十六進制。
jstack 28802 | grep -A 30 7082
# -A 30 顯示匹配行後 30 行(堆棧通常 20-50 行)

"Thread-123" #45 prio=5 os_prio=0 tid=0x00007f8b1c001000 nid=0x7082 runnable [0x00007f8b1d123000]
java.lang.Thread.State: RUNNABLE
at com.example.service.CalcService.calculate(CalcService.java:47)
at com.example.controller.CalcController.calc(CalcController.java:22)
...
nid=0x7082 匹配目標線程。RUNNABLE 狀態且無鎖等待 → 正在執行代碼(非 BLOCKED/WAITING)。CalcService.java:47,打開源碼定位:// CalcService.java 第 47 行附近
while (true) { // 典型死循環
complexCalculation(); // 耗時運算
}
| 模式 | 特徵 | 排查重點 |
|---|---|---|
死循環 while(true)/for(;;) | RUNNABLE、堆棧固定在循環內 | 缺少退出條件、條件永不滿足 |
| 正則回溯災難 | RUNNABLE、堆棧在 Pattern.match/Matcher.find | 惡意輸入觸發指數級回溯,改用非貪婪/原子組 |
| 頻繁 GC | 多線程 GC task thread、CPU 高但業務線程正常 | 堆太小、內存洩漏、大對象頻繁創建 |
| 鎖競爭/自旋 | BLOCKED 或 RUNNABLE 在 synchronized/LockSupport.park | 鎖粒度太大、線程數過多、考慮無鎖數據結構 |
| 日志輸出阻塞 | RUNNABLE 在 FileOutputStream.write/System.out.println | 同步日志、磁盤慢、改異步日志框架 |
# Arthas 線程儀表板,秒級定位熱點方法
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
# 選擇進程 → thread -n 10 顯示前 10 高 CPU 線程堆棧
# async-profiler 生成火焰圖
./profiler.sh -d 30 -f profile.html -e cpu 28802
# 瀏覽器打開 profile.html,直觀定位熱點代碼路徑
while(true),必須有退出條件或 Thread.sleep 降頻。(a+)+,用 PossessiveQuantifier 或限制長度。process_cpu_seconds_total,閾值告警。-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log 便於 GC 分析。ThreadFactoryBuilder.setNameFormat("biz-%d"),堆棧可讀性大增。