使用Arthas生成火焰圖分析性能瓶頸
摘要:通過(guò)Java應(yīng)用診斷利器Arthas生成火焰圖并快速診斷系統(tǒng)性能瓶頸。當(dāng)你握緊Arthas這把手術(shù)刀,每一次線上危機(jī)都是展示技術(shù)深度的舞臺(tái)。
綜述
??有次被借調(diào)到其它項(xiàng)目組負(fù)責(zé)重大版本迭代,涉及改動(dòng)點(diǎn)非常多,開(kāi)發(fā)完成后為了查看系統(tǒng)性能而進(jìn)行壓測(cè),但是壓測(cè)報(bào)告顯示存在Full GC問(wèn)題,由于欠缺解決Full GC問(wèn)題的快速響應(yīng)能力,被項(xiàng)目組負(fù)責(zé)人百般刁難,想著法的PUA。痛定思痛,亡羊補(bǔ)牢,故總結(jié)了如何用Arthas(阿爾薩斯)快速診斷線上如下四個(gè)常見(jiàn)問(wèn)題:
- 不知道哪兒慢:即不知道哪個(gè)函數(shù)占用大量 CPU 時(shí)間。
- 不知道誰(shuí)是瓶頸:即不知道哪個(gè)方法被頻繁調(diào)用導(dǎo)致性能問(wèn)題,如存在鎖競(jìng)爭(zhēng)的熱點(diǎn)函數(shù)大概率是瓶頸。
- 不知道為何輸出結(jié)果不符合預(yù)期。
- 不知道系統(tǒng)應(yīng)用為什么出現(xiàn)FULL GC。
??希望本文對(duì)剛剛接觸系統(tǒng)慢、系統(tǒng)卡或者Full GC問(wèn)題的讀者有所幫助。相對(duì)于傳統(tǒng)工具,Arthas 有三大降維打擊優(yōu)勢(shì):
- 代碼無(wú)侵入診斷:不需要在程序中進(jìn)行額外配置,更不需要手動(dòng)埋點(diǎn)。
- 功能強(qiáng)大:Arthas 提供了四十多種命令,從查看線程調(diào)用鏈,到查看輸入輸出,再到反編譯代碼等,應(yīng)有盡有。
- 占用CPU低:Arthas占用CPU百分比低,而CPU使用率飆高時(shí)根本打不開(kāi)JProfiler。
??攥指成拳固然能聚合力,但也需要每根指頭都發(fā)揮作用,每個(gè)部分都找準(zhǔn)位置。使用 Arthas 生成火焰圖可以幫助我們?nèi)轿豢焖僭\斷 Java 應(yīng)用的性能瓶頸,精細(xì)化掃描 CPU 熱點(diǎn)代碼的位置。下面介紹生成火焰圖的詳細(xì)步驟,假如需要了解更多Arthas信息,請(qǐng)移步官方文檔傳送門(mén)。
判斷是否已安裝 Arthas
??運(yùn)行以下命令查看 Arthas 版本:
$ java -jar arthas-boot.jar --version
Error: Unable to access jarfile arthas-boot.jar
??從上述執(zhí)行結(jié)果得知返回的是 Unable to access jarfile,而不是版本號(hào),表示沒(méi)有安裝 Arthas,故需要安裝;否則,直接運(yùn)行即可。返回版本號(hào)時(shí),終端執(zhí)行結(jié)果如下:
$ java -jar arthas-boot.jar --version
[INFO] JAVA_HOME: /opt/java/openjdk/jre
[INFO] arthas-boot version: 4.0.5
Local versions:
Remote versions:
4.0.5
...
下載和安裝 Arthas
??如果還沒(méi)有安裝 Arthas,可以通過(guò)以下命令下載,分分鐘接入生產(chǎn)環(huán)境:
??Arthas是一個(gè)jar包,下載后直接在根目錄下運(yùn)行java -jar arthas-boot.jar命令啟動(dòng)已經(jīng)安裝的Arthas:
$ java -jar arthas-boot.jar
[INFO] JAVA_HOME: /opt/java/openjdk/jre
[INFO] arthas-boot version: 4.0.5
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 1 /com.example.MyApp-0.0.1-SNAPSHOT.jar
??Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER的意思是arthas可以自動(dòng)識(shí)別Java進(jìn)程,我們?cè)谶x擇進(jìn)程號(hào)后單擊回車(chē)鍵即可。
選擇目標(biāo)進(jìn)程
??Arthas啟動(dòng)后會(huì)自動(dòng)掃描當(dāng)前運(yùn)行的 Java 進(jìn)程,輸入對(duì)應(yīng)的進(jìn)程編號(hào)并單擊回車(chē)鍵即可選中你要分析的應(yīng)用。輸入進(jìn)程號(hào) 1 并回車(chē)就是選擇目標(biāo) com.example.MyApp。此時(shí)此刻就會(huì)安裝arthas:
// 首次啟動(dòng)時(shí),需要下載arthas,再次啟動(dòng)就沒(méi)有downloading提示了
[INFO] File size: 13.75 MB, downloaded size: 13.70 MB, downloading ...
[INFO] Download arthas success.
[INFO] arthas home: /root/.arthas/lib/4.0.5/arthas
[INFO] Try to attach process 1
Picked up JAVA_TOOL_OPTIONS:
[INFO] Attach process 1 success.
[INFO] arthas-client connect 127.0.0.1 3658
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki https://arthas.aliyun.com/doc
tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html
version 4.0.5
main_class /com.example.MyApp-0.0.1-SNAPSHOT.jar -Dfile.encoding=utf-8
--spring.profiles.active=test
pid 1
start_time 2025-08-04 02:17:15.480
currnt_time 2025-08-04 03:07:06.750
[arthas@1]$
使用profiler生成火焰圖
??在一個(gè)Java應(yīng)用中使用 Arthas 的 profiler 命令生成火焰圖(Flame Graph)。執(zhí)行Arthas 的 profiler 命令可以生成火焰圖,支持 CPU 和內(nèi)存分析。通過(guò)該命令可以輕輕松松地獲取Java應(yīng)用的運(yùn)行狀態(tài)和熱點(diǎn)分布,幫助你快速定位問(wèn)題并制定優(yōu)化方案。執(zhí)行以下命令開(kāi)始 CPU 采樣:
[arthas@1]$ profiler start
Profiling started
??上述命令表示啟動(dòng)Profiler,在默認(rèn)的情況下生成CPU的火焰圖;如果你需要分析內(nèi)存分配情況,可以使用如下命令:
$ profiler start -e alloc
??等待采樣完成。默認(rèn)情況下,Arthas 會(huì)持續(xù)采樣,直到你手動(dòng)停止??梢栽O(shè)置倒計(jì)時(shí)只采集一段時(shí)間(例如 30 秒或 1 分鐘),倒計(jì)時(shí)結(jié)束時(shí)自動(dòng)停止采樣。例如執(zhí)行以下命令表示采樣300秒并生成火焰圖:
[arthas@1]$ profiler start -d 300
Profiling started
profiler will silent stop after 300 seconds.
profiler output file will be: /arthas-output/20250804-031051.html
??生成的火焰圖默認(rèn)是一個(gè) .html 文件,保存到應(yīng)用工作目錄下的arthas-output文件夾,并在控制臺(tái)輸出文件保存路徑。也可以使用format命令指定生成HTML格式的文件,只需要在stop后面輸入-format html就可以了,語(yǔ)法糖如下:
[arthas@1]$ profiler stop -format html
OK
profiler output file: /arthas-output/20250804-031053.html
??這時(shí)會(huì)生成一個(gè)html的文件 /arthas-output/20250804-031053.html,把火焰圖從服務(wù)器下載到本地后可以通過(guò)瀏覽器直接打開(kāi)后,能看到這樣的結(jié)構(gòu):

查看火焰圖
??火焰圖(Flame Graph)是一種用于透視函數(shù)調(diào)用棧的工具,它將函數(shù)調(diào)用關(guān)系以圖形化的方式展示出來(lái),幫助開(kāi)發(fā)者更直觀地理解程序的執(zhí)行流程。在火焰圖中,每一個(gè)矩形條代表一個(gè)函數(shù)調(diào)用的棧幀,矩形的寬度表示該函數(shù)調(diào)用的時(shí)間占比,顏色則可以根據(jù)需要進(jìn)行自定義。
??下面介紹如何解讀火焰圖X軸與Y軸的含義。
??X 軸:表示函數(shù)采樣次數(shù)的分布,也可以理解為CPU時(shí)間的分布比例。某個(gè)函數(shù)在采樣中出現(xiàn)次數(shù)越多寬度越寬,說(shuō)明該函數(shù)被采集的次數(shù)越多,占用的CPU時(shí)間越長(zhǎng)。各個(gè)函數(shù)按字母順序排列,沒(méi)有時(shí)間上的先后關(guān)系。
??Y 軸:表示調(diào)用棧層級(jí),每一層代表一個(gè)函數(shù)。調(diào)用棧越深,火焰就越高,頂部顯示的是當(dāng)前正在執(zhí)行的函數(shù),而下方則是它的父函數(shù)。
??顏色:不同顏色表示不同的函數(shù),顏色沒(méi)有特殊含義,主要是為了區(qū)分。因?yàn)榛鹧鎴D表示的是cpu的繁忙程度,所以一般選擇暖色調(diào)。
??通過(guò)火焰圖可以快速定位性能瓶頸。主要看火焰圖頂層的函數(shù),占據(jù)寬度最大的函數(shù)就可能存在性能瓶頸,需要對(duì)它進(jìn)行一個(gè)深度的性能分析,通過(guò)邏輯優(yōu)化盡最大可能削去平頂。
???常用選項(xiàng)
profiler:生成熱點(diǎn)火焰圖。
profiler start -d 30: 開(kāi)始對(duì)應(yīng)用中當(dāng)前執(zhí)行的活動(dòng)采樣 30 秒,采樣結(jié)束后默認(rèn)會(huì)生成 HTML 文件
分析內(nèi)存分配:如果你想分析內(nèi)存分配,可以使用profiler start -e alloc。
profiler -h:查看幫助選項(xiàng)。
profiler list:展示支持的全部事件。
profiler status:打印采樣功能已經(jīng)運(yùn)行了多少秒,只能在采樣期間執(zhí)行。
profiler getSamples:查詢已經(jīng)獲取的樣本數(shù)量,只能在采樣期間執(zhí)行。
jad:反編譯指定已加載類的源碼。
trace:查看方法調(diào)用鏈路上每個(gè)節(jié)點(diǎn)的耗時(shí)。
watch:查看方法請(qǐng)求參數(shù)和返回值等。
memory:查看 JVM 的內(nèi)存信息。
sysenv:查看 JVM 的環(huán)境變量。
quit或exit:退出當(dāng)前監(jiān)控的進(jìn)程,不會(huì)影響其他進(jìn)程監(jiān)控。
stop:停止所有監(jiān)控和跟蹤并退出Arthas。
dashboard:查看當(dāng)前進(jìn)程的實(shí)時(shí)數(shù)據(jù)面板。
thread:查看當(dāng)前線程信息,包括堆棧信息。
sysprop:查看和修改JVM的系統(tǒng)屬性。
sysenv:查看JVM的環(huán)境變量。
cls:清空當(dāng)前屏幕區(qū)域的內(nèi)容。
示例
??下面梳理出一個(gè)完整的、生成火焰圖的示例:
// 啟動(dòng) Arthas
$java -jar arthas-boot.jar
// 選擇目標(biāo) Java 進(jìn)程(例如輸入 1)并單擊回車(chē)鍵
1
[arthas@1]$ profiler status// 查詢當(dāng)前的采樣狀態(tài)
Profiler is not active
[arthas@1]$ profiler start// 開(kāi)始 CPU 采樣
Profiling started
[arthas@1]$ profiler status
Profiling is running for 5 seconds
[arthas@1]$ profiler getSamples
82
[arthas@1]$ profiler stop // 等待一段時(shí)間后停止采樣并生成火焰圖
OK
profiler output file: /arthas-output/20250801-031533.html
[arthas@1]$ profiler list
Basic events:
cpu
alloc
nativemem
lock
wall
itimer
ctimer
Java method calls:
ClassName.methodName
??希望這個(gè)案例能幫助你順利生成火焰圖并分析和解決應(yīng)用性能問(wèn)題!使用過(guò)程中,需要注意以下事項(xiàng):
- 性能影響:火焰圖是通過(guò)采樣生成的,頻繁使用可能會(huì)影響應(yīng)用性能,建議在測(cè)試環(huán)境中使用。
- 文件下載:生成的火焰圖需要從服務(wù)器下載到本地才能查看。
- 權(quán)限問(wèn)題:確保你有權(quán)限訪問(wèn)目標(biāo) Java 進(jìn)程和生成文件的目錄。你如果沒(méi)有權(quán)限,就去求助運(yùn)維。
CPU使用率高
??CPU 是程序運(yùn)行的核心計(jì)算資源,一旦出現(xiàn) CPU 使用率過(guò)高的現(xiàn)象,必定對(duì)大部分用戶的訪問(wèn)耗時(shí)產(chǎn)生影響。針對(duì)這類問(wèn)題亟需快速定位出有問(wèn)題的線程,并獲取該線程當(dāng)前執(zhí)行的代碼位置。雖然使用 top + jstack 命令可以定位這類問(wèn)題,但是Arthas 提供了更便捷的一體化工具dashboard,用于查看當(dāng)前系統(tǒng)的實(shí)時(shí)數(shù)據(jù)面板。
# 調(diào)用線程看板,并刷新數(shù)據(jù)三次
[arthas@1]$ dashboard -n 3
??執(zhí)行結(jié)果如下:

??dashboard 刷新三次后,就要找出 CPU 占用最高的用戶線程的 ID進(jìn)行把脈。示例中沒(méi)有異常線程,我們用命令thread 31 看看線程31執(zhí)行情況:

??當(dāng)線程調(diào)用 AbstractQueuedSynchronizer 的方法awaitNanos()或await()時(shí),會(huì)進(jìn)入TIMED_WAITING狀態(tài)。此時(shí)線程會(huì)暫停執(zhí)行,直到超時(shí)或有條件的被觸發(fā)。這種機(jī)制常用于定時(shí)任務(wù)、資源競(jìng)爭(zhēng)等場(chǎng)景。
jad字節(jié)碼文件反編譯成源代碼
??驗(yàn)證本地修改的代碼是否推送到線上環(huán)境。有些時(shí)候你會(huì)發(fā)現(xiàn)測(cè)試環(huán)境一切正常,但生產(chǎn)環(huán)境就報(bào)錯(cuò)了。這類問(wèn)題主要靠做好上線流程的管控,問(wèn)題根源可能是打包的依賴庫(kù)出現(xiàn)沖突,造成程序行為不一致;也可能是沒(méi)有把最新代碼發(fā)到線上。接下來(lái),我們看看怎么用 Arthas 命令jad反編譯字節(jié)碼文件為Java源代碼并查看是否存在發(fā)錯(cuò)版本問(wèn)題。
??在Arthas中,jad命令提供了多種參數(shù)以滿足不同場(chǎng)景的訴求。我們以Java類java.lang.String為例,演示如何使用jad進(jìn)行反編譯。
# 快速反編譯整個(gè)類文件
jad java.lang.String
# 顯示行號(hào),調(diào)試?yán)?jad --lineNumber true java.lang.String
??jad可以用于精準(zhǔn)反編譯指定函數(shù):如 jad java.lang.String hashCode 僅反編譯java.lang.String類的hashCode方法:

??如果您想要反編譯特定類的某個(gè)方法并保存反編譯結(jié)果到指定目錄,您可以這樣操作:
jad --source-only packageName.MyClass myMethod -d /path/to/save/directory/myMethod.java
??這將只顯示在package為packageName的類MyClass中方法名為myMethod的源代碼,并且不會(huì)包含行號(hào)信息,同時(shí)將反編譯過(guò)程中產(chǎn)生的臨時(shí)class文件保存到/path/to/save/directory。
stack 輸出方法被調(diào)用的調(diào)用路徑鏈
??命令stack用于輸出指定方法被調(diào)用的調(diào)用路徑。開(kāi)發(fā)過(guò)程中,經(jīng)常遇到一個(gè)核心方法被多個(gè)方法調(diào)用,導(dǎo)致執(zhí)行的路徑非常多,不知道這個(gè)方法是從哪里被執(zhí)行;如果想知道被誰(shuí)調(diào)用了,就可以使用stack。語(yǔ)法糖如下:
stack packageName.MyClass myMethod
trace
??trace:追蹤方法內(nèi)部調(diào)用,即輸出指定方法內(nèi)每個(gè)調(diào)用節(jié)點(diǎn)上的耗時(shí), 每次只能跟蹤一級(jí)方法的調(diào)用鏈路,不能追蹤子函數(shù)的耗時(shí)情況。語(yǔ)法糖:
// 統(tǒng)計(jì)myMethod的調(diào)用路徑耗時(shí)
trace packageName.MyClass myMethod
// 根據(jù)調(diào)用耗時(shí),找出大于200ms的調(diào)用路徑
trace packageName.MyClass myMethod '#cost>200'
??如果一個(gè)函數(shù)耗時(shí)非常高,會(huì)高亮展示。下圖中一個(gè)函數(shù)的執(zhí)行耗時(shí)占整體的99.85%,所以,高亮展示了:
??例如用trace查看traceTest的耗時(shí)情況時(shí),無(wú)法追蹤到doSth101()的耗時(shí)。
package
public void traceTest() {
doSth1();
doSth2();
doSth3();
doSth5();
}
public void doSth1() {
doSth100();
doSth101();
}
性能分析
??遇到性能問(wèn)題時(shí),不要囿于思維局限,請(qǐng)?zhí)鲎约业摹耙划€三分地”,按照以下步驟排查:
# 1. 概覽全局,尋找蛛絲馬跡
dashboard -i 5000
# 2. 生成火焰圖定位CPU熱點(diǎn)
profiler start
profiler stop
# 3. 追蹤慢方法
trace *StringUtils substring '#cost>100'
# 4. 免重啟熱更新(大神展示技術(shù)深度的舞臺(tái),小白不要碰,別搞砸了線上環(huán)境)
redefine 把新生成的字節(jié)碼文件在內(nèi)存中執(zhí)行
??破解線程阻塞之謎。如果遇到線程阻塞問(wèn)題,可以使用thread排查:
# 1. 查看線程狀態(tài)分布
thread -b # 顯示阻塞線程
# 2. 監(jiān)控鎖競(jìng)爭(zhēng)情況
watch java.util.concurrent.locks.ReentrantLock getQueueLength
總結(jié)
??本文介紹了一些arthas常用命令的參數(shù)和具體使用方法,幫助大家進(jìn)一步掌握arthas的使用,成為java在線診斷高手。Arthas能力矩陣:
| 問(wèn)題類型 | 核心命令 | 備注 |
|---|---|---|
| 宏觀把脈 | dashboard | 概覽 |
| 定位CPU熱點(diǎn) | profiler start/stop | 生成火焰圖 |
| 方法級(jí)追蹤 | trace/watch | 精確到毫秒的性能分析 |
| 線程診斷 | thread/thread -b | 秒級(jí)定位線程阻塞源頭 |
| 內(nèi)存分析 | heapdump/vmtool | 不觸發(fā)GC的內(nèi)存快照 |
| 動(dòng)態(tài)修復(fù) | jad/redefine | 免重啟熱更新 |
??感謝大家看到最后,如文章有不足,歡迎大家在評(píng)論區(qū)支持,給予意見(jiàn)。如果覺(jué)得我的文章對(duì)你有幫助,那就給我點(diǎn)個(gè)贊吧。
Reference
Buy me a coffee. ?Get red packets.
浙公網(wǎng)安備 33010602011771號(hào)