線上CPU飆到100%?別慌,這3個工具比top快10倍!
正準備下班,手機突然瘋狂震動——生產環境CPU告警!你SSH登上服務器,習慣性地敲下top命令,然後按H切換到線程視圖,找到最高CPU的線程,記下PID,轉成16進制,再jstack...等你一套操作敲完,5秒過去了,CPU使用率已經降下來了。線索,就這樣在指尖溜走。
🔥 傳統方法的三宗罪
説起定位Java應用CPU使用率高的問題,大部分人的第一反應是:top + jstack。
完整流程是這樣的:
# 第一步:找到Java進程
top
# 第二步:切換到線程視圖
按 H 鍵
# 第三步:記下最高CPU的線程ID(比如12345)
# 第四步:轉16進制
printf "%x\n" 12345
# 輸出:3039
# 第五步:dump線程棧
jstack <pid> > thread.txt
# 第六步:在文件中搜索 nid=0x3039
grep -A 20 "nid=0x3039" thread.txt
看起來很專業?但有三個致命問題:
1. 命令長,記不住
老實説,你能背下printf "%x\n"這個16進制轉換命令嗎?反正我每次都要Google一下。遇到線上問題緊張得不行,大腦容易空白,這種命令根本記不住。
2. 流程長,容易出錯
6個步驟,任何一步出錯都要重來。線程ID記錯了?16進制轉換算錯了?grep的時候少打個0?每一步都是坑。
3. 時機錯過,白忙一場
這是最要命的。
CPU飆高往往是瞬時的——可能是某個定時任務觸發,可能是流量突增,也可能是GC導致。等你把上面這6步命令敲完,5秒過去了,CPU使用率已經降下來,線程棧里根本看不出問題所在。
就像拿着相機想拍流星,結果還在調焦距,流星已經沒了。
我去年就遇到過一次:電商大促期間,訂單服務CPU間歇性飆到90%,每次持續2-3秒。等我SSH登上去,top剛打開,CPU就降下來了。來來回回折騰了半小時,一無所獲,最後只能擴容服務器硬抗。
那次之後,我決定找更快的工具。
🚀 工具1:show-busy-java-threads.sh - 一鍵救場
為什麼推薦它?
show-busy-java-threads.sh是一個Shell腳本,把上面那6步操作自動化了。一行命令,秒級出結果。
完整的腳本鏈接,關注公眾號回覆 thread 可下載。
真實案例:秒級定位死循環
去年有一次,推薦服務的CPU突然飆到300%(4核機器),持續了10多分鐘。用户反饋首頁加載特別慢。
我第一時間想到了這個腳本:
# 直接運行,不需要傳任何參數
./show-busy-java-threads.sh
瞬間結果就出來了:
[1] Busy(57.0%) thread(23645/0x5c5d) stack of java process(23630):
"recommendation-worker-3" #123 daemon prio=5 os_prio=0 tid=0x00007f8a1c0a1000 nid=0x5c5d runnable
at com.company.recommend.ContentFilter.isValid(ContentFilter.java:156)
at com.company.recommend.RecommendEngine.filter(RecommendEngine.java:89)
at com.company.recommend.RecommendEngine.process(RecommendEngine.java:45)
...
[2] Busy(55.2%) thread(23648/0x5c60) stack of java process(23630):
"recommendation-worker-6" #126 daemon prio=5 os_prio=0 tid=0x00007f8a1c0a3800 nid=0x5c60 runnable
at com.company.recommend.ContentFilter.isValid(ContentFilter.java:156)
...
一眼就看出來了:
- 多個工作線程都卡在
ContentFilter.isValid這個方法的156行 - CPU佔用分別是57%和55%
打開代碼一看,第156行是個正則表達式匹配,而且是在一個沒有break的循環裏...找到問題了!
從發現問題到定位代碼,很快就搞定。
使用方法
1. 運行腳本:
# 自動識別Java進程,顯示最繁忙的5個線程
./show-busy-java-threads.sh
# 顯示最繁忙的10個線程
./show-busy-java-threads.sh -c 10
# 指定Java進程ID
./show-busy-java-threads.sh -p 12345
# 持續監控模式,每3秒刷新一次
./show-busy-java-threads.sh -c 5 -i 3
2. 輸出解讀:
腳本會輸出:
- 線程ID(十進制和十六進制)
- CPU佔用百分比
- 完整的線程棧
- 自動高亮顯示業務代碼
優勢:
- ✅ 不用記命令,一行搞定
- ✅ 不用轉16進制,自動完成
- ✅ 不用grep,直接顯示
- ✅ 支持持續監控,不錯過瞬時高峯
適用場景:
適合在生產環境快速救場,尤其是CPU飆高持續時間較短的場景。放在服務器上常備,關鍵時刻能救命。
📊 工具2:fastthread.io - 在線分析專家
為什麼推薦它?
如果説show-busy-java-threads.sh是"急救箱",那fastthread.io就是"體檢中心"。
它是一個在線的線程棧分析工具,可以把jstack dump出的線程棧文件上傳上去,生成一份詳細的可視化報告。不僅能看CPU,還能發現死鎖、線程泄漏、阻塞等問題。
什麼時候用它?
當你已經用jstack dump了線程棧,但是:
- 線程太多(幾千個),肉眼根本看不過來
- 想分析線程的整體狀態分佈
- 懷疑有死鎖或者阻塞
- 想生成報告給其他人看
我一般會這樣用:
- 先用show-busy-java-threads.sh快速定位
- 如果問題不明顯,再用jstack dump完整線程棧
- 扔到fastthread.io深度分析
使用步驟
1. 生成線程棧文件:
# 獲取Java進程ID
jps -l
# dump線程棧(帶鎖信息)
jstack -l <pid> > thread_dump.txt
2. 訪問fastthread.io並上傳:
打開 https://fastthread.io/,把thread_dump.txt文件拖進去,幾秒鐘就能生成報告。
3. 查看分析報告:
報告包含這些內容:
① 線程總覽:
Total Threads: 156
Runnable: 8
Waiting: 120
Timed_Waiting: 25
Blocked: 3
② CPU熱點線程:
自動識別出佔用CPU最高的線程,按百分比排序。
③ 線程分組統計:
按線程名前綴分組,比如:
http-nio-8080-exec: 50 threads
scheduling-: 10 threads
能快速看出哪個線程池的線程最多。
④ 死鎖檢測:
如果有死鎖,會高亮顯示並畫出依賴關係圖。
⑤ 阻塞分析:
統計哪些線程在等待同一把鎖,幫你發現鎖競爭問題。
實戰技巧
技巧1:多次dump對比
CPU飆高時,連續dump 3次線程棧(間隔1秒),分別上傳到fastthread.io。如果某個方法在3次dump中都出現,基本可以確定就是它了。
jstack <pid> > dump1.txt
sleep 1
jstack <pid> > dump2.txt
sleep 1
jstack <pid> > dump3.txt
技巧2:關注RUNNABLE狀態
CPU高的時候,重點看狀態是RUNNABLE的線程。WAITING和TIMED_WAITING的線程不佔CPU。
優勢:
- ✅ 可視化報告,一目瞭然
- ✅ 自動檢測死鎖、阻塞等問題
- ✅ 無需安裝,瀏覽器就能用
- ✅ 支持多次dump對比分析
適用場景:
適合事後分析、深度排查、生成報告給團隊共享。對於複雜的線程問題(死鎖、線程泄漏),效果特別好。
🔧 工具3:Arthas - 阿里開源的診斷神器
為什麼推薦它?
Arthas是阿里開源的Java診斷工具,功能非常強大,堪稱"瑞士軍刀"。不僅能定位CPU問題,還能:
- 反編譯線上代碼
- 實時修改日誌級別
- 查看方法調用耗時
- 查看類加載信息
- ...
我在之前的文章《線程暴增20K+!一次驚心動魄的Jenkins性能排查之旅》裏,就是用Arthas的classloader命令找到了問題的根源。
對於CPU問題,Arthas的thread命令特別好用。
使用方法
1. 安裝並啓動:
# 下載arthas-boot.jar
curl -O https://arthas.aliyun.com/arthas-boot.jar
# 啓動並attach到Java進程
java -jar arthas-boot.jar
啓動後會列出當前所有Java進程,輸入序號選擇要診斷的進程:
[INFO] arthas-boot version: 3.7.1
[INFO] Found existing java process, please choose one and input the serial number.
* [1]: 12345 com.company.Application
[2]: 23456 org.elasticsearch.bootstrap.Elasticsearch
1
2. 查看CPU佔用最高的線程:
# 顯示CPU佔用最高的3個線程
thread -n 3
輸出示例:
"http-nio-8080-exec-123" Id=156 RUNNABLE (in native)
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
...
"background-task-2" Id=89 RUNNABLE
at com.company.service.DataProcessor.process(DataProcessor.java:234)
at com.company.service.DataProcessor.run(DataProcessor.java:156)
at java.lang.Thread.run(Thread.java:748)
CPU佔用: 45.3%
3. 持續監控某個線程:
# 查看指定線程ID的詳細信息
thread 156
# 每3秒刷新一次
thread -i 3000
4. 查看所有線程的狀態分佈:
thread
會輸出:
Threads Total: 156, NEW: 0, RUNNABLE: 8, BLOCKED: 3, WAITING: 120, TIMED_WAITING: 25
ID NAME GROUP PRIORITY STATE %CPU TIME
89 background-task-2 main 5 RUNNABLE 45.3 0:23.567
156 http-nio-8080-exec-123 main 5 RUNNABLE 12.1 0:08.234
...
優勢:
- ✅ 實時交互,無需重啓應用
- ✅ 功能豐富,一個工具頂十個
- ✅ 不影響性能,可以在生產環境使用
- ✅ 社區活躍,文檔齊全
適用場景:
適合複雜問題的深度診斷,尤其是需要實時交互、方法追蹤的場景。是每個Java工程師都應該掌握的神器。
📋 三個工具對比總結
| 工具 | 上手難度 | 速度 | 功能豐富度 | 使用場景 |
|---|---|---|---|---|
| show-busy-java-threads.sh | ⭐ 極簡 | ⚡️ 秒級 | ⭐⭐ 單一 | 生產環境快速救場 |
| fastthread.io | ⭐⭐ 簡單 | ⚡️⚡️ 分鐘級 | ⭐⭐⭐ 中等 | 事後深度分析、生成報告 |
| Arthas | ⭐⭐⭐ 需學習 | ⚡️ 實時 | ⭐⭐⭐⭐⭐ 最強 | 複雜問題診斷、實時監控 |
我的推薦:
- 新手:先學show-busy-java-threads.sh,一行命令搞定90%的場景
- 進階:學會fastthread.io,應對複雜的線程問題
- 老手:必須掌握Arthas,這是Java工程師的核心競爭力
組合使用效果最佳:
- 問題發生時,先用
show-busy-java-threads.sh快速定位 - 如果一次看不出問題,dump完整線程棧扔到
fastthread.io分析 - 需要深度排查時,用
Arthas的thread、trace、watch等命令追蹤方法
💡 最後的建議
1. 別等出問題再學工具
很多人都是這樣:線上出問題了,才開始Google"怎麼定位CPU高",然後手忙腳亂。
正確做法:
- 在測試環境提前把這三個工具裝好、玩熟
- 寫個死循環程序,模擬CPU飆高,練習定位
- 把常用命令整理成checklist,貼在工位上
關鍵時刻,肌肉記憶能救命。
2. 監控告警要先行
工具再好,也只是"事後諸葛亮"。更重要的是提前發現問題:
- 配置CPU使用率告警(建議閾值70%)
- 配置JVM線程數告警
- 配置接口響應時間告警
發現得越早,處理越從容。
3. 養成記錄習慣
每次排查完問題,記得寫個覆盤文檔:
- 問題現象
- 定位過程
- 根本原因
- 解決方案
不是為了寫而寫,是為了下次遇到類似問題,能快速找到解決思路。
📮 我是穩哥,深耕Java和中間件領域多年,專注分享實戰經驗和技術乾貨。
💡 關注我,一起探索:
- 🔧 線上問題排查實戰
- ⚡ 性能優化真實案例
- 🎯 架構設計最佳實踐
- 🚀 從原理到實戰的技術深度剖析
關注公眾號【穩哥的隨筆】,讓我們一起在技術的道路上不斷精進! 🚀
如果這篇文章對你有幫助,歡迎點贊、收藏、轉發,你的支持是我創作的最大動力!