實(shí)戰(zhàn)!使用 阿里 Arthas 工具分析 CPU 飆高
Arthas是阿里開源的 Java 診斷工具,相比 JDK 內(nèi)置的診斷工具,要更人性化,并且功能強(qiáng)大,可以實(shí)現(xiàn)許多問題的一鍵定位,而且可以一鍵反編譯類查看源碼,甚至是直接進(jìn)行生產(chǎn)代碼熱修復(fù),實(shí)現(xiàn)在一個(gè)工具內(nèi)快速定位和修復(fù)問題的一站式服務(wù)。
今天,我就帶你使用 Arthas 定位一個(gè) CPU 使用高的問題,系統(tǒng)學(xué)習(xí)下這個(gè)工具的使用。
首先,下載并啟動(dòng) Arthas:
curl -O https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
啟動(dòng)后,直接找到我們要排查的 JVM 進(jìn)程,然后可以看到 Arthas 附加進(jìn)程成功:
[INFO] arthas-boot version: 3.1.7
[INFO] Found existing java process, please choose one and hit RETURN.
* [1]: 12707
[2]: 30724 org.jetbrains.jps.cmdline.Launcher
[3]: 30725 org.geekbang.time.commonmistakes.troubleshootingtools.highcpu.HighCPUApplication
[4]: 24312 sun.tools.jconsole.JConsole
[5]: 26328 org.jetbrains.jps.cmdline.Launcher
[6]: 24106 org.netbeans.lib.profiler.server.ProfilerServer
3
[INFO] arthas home: /Users/zhuye/.arthas/lib/3.1.7/arthas
[INFO] Try to attach process 30725
[INFO] Attach process 30725 success.
[INFO] arthas-client connect 127.0.0.1 3658
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki https://alibaba.github.io/arthas
tutorials https://alibaba.github.io/arthas/arthas-tutorials
version 3.1.7
pid 30725
time 2020-01-30 15:48:33
輸出 help 命令,可以看到所有支持的命令列表。今天,我們會(huì)用到 dashboard、thread、jad、watch、ognl 命令,來定位這個(gè) HighCPUApplication 進(jìn)程。你可以通過官方文檔:https://arthas.aliyun.com/doc/commands.html,查看這些命令的完整介紹:
圖片
dashboard 命令用于整體展示進(jìn)程所有線程、內(nèi)存、GC 等情況,其輸出如下:
圖片
可以看到,CPU 高并不是 GC 引起的,占用 CPU 較多的線程有 8 個(gè),其中 7 個(gè)是 ForkJoinPool.commonPool。
ForkJoinPool.commonPool 是并行流默認(rèn)使用的線程池。
所以,此次 CPU 高的問題,應(yīng)該出現(xiàn)在某段并行流的代碼上。
接下來,要查看最繁忙的線程在執(zhí)行的線程棧,可以使用 thread -n 命令。這里,我們查看下最忙的 8 個(gè)線程:
thread -n 8
輸出如下:
圖片
可以看到,由于這些線程都在處理 MD5 的操作,所以占用了大量 CPU 資源。我們希望分析出代碼中哪些邏輯可能會(huì)執(zhí)行這個(gè)操作,所以需要從方法棧上找出我們自己寫的類,并重點(diǎn)關(guān)注。
由于主線程也參與了 ForkJoinPool 的任務(wù)處理,因此我們可以通過主線程的棧看到需要重點(diǎn)關(guān)注 org.geekbang.time.commonmistakes.troubleshootingtools.highcpu.HighCPUApplication 類的 doTask 方法。
接下來,使用 jad 命令直接對 HighCPUApplication 類反編譯:
jad org.geekbang.time.commonmistakes.troubleshootingtools.highcpu.HighCPUApplication
可以看到,調(diào)用路徑是 main->task()->doTask(),當(dāng) doTask 方法接收到的 int 參數(shù)等于某個(gè)常量的時(shí)候,會(huì)進(jìn)行 1 萬次的 MD5 操作,這就是耗費(fèi) CPU 的來源。那么,這個(gè)魔法值到底是多少呢?
圖片
你可能想到了,通過 jad 命令繼續(xù)查看 User 類即可。這里因?yàn)槭?Demo,所以我沒有給出很復(fù)雜的邏輯。在業(yè)務(wù)邏輯很復(fù)雜的代碼中,判斷邏輯不可能這么直白,我們可能還需要分析出 doTask 的“慢”會(huì)慢在什么入?yún)⑸稀?/p>
這時(shí),我們可以使用 watch 命令來觀察方法入?yún)?。如下命令,表示需要監(jiān)控耗時(shí)超過 100 毫秒的 doTask 方法的入?yún)ⅲ⑶逸敵鋈雲(yún)?,展開 2 層入?yún)?shù):
watch org.geekbang.time.commonmistakes.troubleshootingtools.highcpu.HighCPUApplication doTask '{params}' '#cost>100' -x 2
可以看到,所有耗時(shí)較久的 doTask 方法的入?yún)⒍际?0,意味著 User.ADMN_ID 常量應(yīng)該是 0。
圖片
最后,我們使用 ognl 命令來運(yùn)行一個(gè)表達(dá)式,直接查詢 User 類的 ADMIN_ID 靜態(tài)字段來驗(yàn)證是不是這樣,得到的結(jié)果果然是 0:
[arthas@31126]$ ognl '@org.geekbang.time.commonmistakes.troubleshootingtools.highcpu.User@ADMIN_ID'
@Integer[0]
需要額外說明的是,由于 monitor、trace、watch 等命令是通過字節(jié)碼增強(qiáng)技術(shù)來實(shí)現(xiàn)的,會(huì)在指定類的方法中插入一些切面來實(shí)現(xiàn)數(shù)據(jù)統(tǒng)計(jì)和觀測,因此診斷結(jié)束要執(zhí)行 shutdown 來還原類或方法字節(jié)碼,然后退出 Arthas。
在這個(gè)案例中,我們通過 Arthas 工具排查了高 CPU 的問題:
- 首先,通過 dashboard + thread 命令,基本可以在幾秒鐘內(nèi)一鍵定位問題,找出消耗 CPU 最多的線程和方法棧;
- 然后,直接 jad 反編譯相關(guān)代碼,來確認(rèn)根因;
- 此外,如果調(diào)用入?yún)⒉幻鞔_的話,可以使用 watch 觀察方法入?yún)?,并根?jù)方法執(zhí)行時(shí)間來過濾慢請求的入?yún)ⅰ?/li>
可見,使用 Arthas 來定位生產(chǎn)問題根本用不著原始代碼,也用不著通過增加日志來幫助我們分析入?yún)?,一個(gè)工具即可完成定位問題、分析問題的全套流程。
對于應(yīng)用故障分析,除了阿里 Arthas 之外,還可以關(guān)注去哪兒的Bistoury :https://github.com/qunarcorp/bistoury工具,其提供了可視化界面,并且可以針對多臺機(jī)器進(jìn)行管理,甚至提供了在線斷點(diǎn)調(diào)試等功能,模擬 IDE 的調(diào)試體驗(yàn)。