Arthas排查jvm相關(guān)問題的利器
背景:
一次線上問題的綜合排查排查,兩個(gè)相同的系統(tǒng)的某個(gè)模塊,數(shù)據(jù)量更少的系統(tǒng)查詢更慢。
先說下整體思路:
查看系統(tǒng)整理負(fù)載,網(wǎng)絡(luò)有100左右毫秒的延遲,看起來影響不大
查看正序運(yùn)行整體情況,一次查詢會(huì)經(jīng)歷3次FGC,有問題,解決后雖然會(huì)快一些但是還是有點(diǎn)慢
跟蹤調(diào)用棧,發(fā)現(xiàn)有點(diǎn)在獲查詢結(jié)果的時(shí)候特別慢
排查是否有mysql慢查詢(定義為1秒以上的查詢),結(jié)果是沒有
通過arthas發(fā)現(xiàn)調(diào)用了20多次查詢,每次網(wǎng)絡(luò)延遲100ms,加起來就有2s了,定位出問題。
這樣看起來arthas真的很強(qiáng)大,它不僅僅能處理上述問題,還能….
這個(gè)類從哪個(gè) jar 包加載的?為什么會(huì)報(bào)各種類相關(guān)的 Exception?
我改的代碼為什么沒有執(zhí)行到?難道是我沒 commit?分支搞錯(cuò)了?
遇到問題無法在線上 debug,難道只能通過加日志再重新發(fā)布嗎?
線上遇到某個(gè)用戶的數(shù)據(jù)處理有問題,但線上同樣無法 debug,線下無法重現(xiàn)!
是否有一個(gè)全局視角來查看系統(tǒng)的運(yùn)行狀況?
有什么辦法可以監(jiān)控到JVM的實(shí)時(shí)運(yùn)行狀態(tài)?
代碼出現(xiàn)異常,怎么能獲取請(qǐng)求參數(shù)和返回值。
Arthas到底是什么
Arthas 是Alibaba開源的Java診斷工具,采用命令行交互模式,提供了豐富的功能,是排查jvm相關(guān)問題的利器。
在逛github時(shí),發(fā)現(xiàn)了這款利器,深入了解之后,簡直驚為天人。下面先列舉一下它能做的一些事情:
提供性能看板,包括線程、cpu、內(nèi)存等信息,并且會(huì)定時(shí)的刷新。
根據(jù)各種條件查看線程快照。比如找出cpu占用率最高的n個(gè)線程等
輸出jvm的各種信息,如gc算法、jdk版本、ClassPath等
查看/設(shè)置sysprop和sysenv
查看某個(gè)類的靜態(tài)屬性,也可以通過ognl語法執(zhí)行一些語句
查看已加載的類的詳細(xì)信息,比如這個(gè)類從哪個(gè)jar包加載的。也可以查看類的方法的信息
dump某個(gè)類的字節(jié)碼到指定目錄
直接反編譯指定的類
查看類加載器的一些信息
可以讓jvm重新加載某個(gè)類
監(jiān)控方法的執(zhí)行,同時(shí)可以獲取到執(zhí)行的入?yún)?、出參以及拋出的異?/p>
追蹤方法執(zhí)行的調(diào)用棧,以及各個(gè)方法的調(diào)用時(shí)間
Arthas運(yùn)行原理

Arthas命令列表

athas的各個(gè)命令
sc 和 sm
通過sc可以查看已加載類的相關(guān)信息,比如該類是從哪個(gè)jar包加載的,被哪個(gè)類加載器加載的,以及是否是接口等等。
sm查看已加載類的方法詳情。
dashboard
進(jìn)入當(dāng)前系統(tǒng)的實(shí)時(shí)數(shù)據(jù)面板,按 ctrl+c 退出。這個(gè)面板會(huì)實(shí)時(shí)刷新,其中包括線程信息、內(nèi)存信息、gc信息、還有一些運(yùn)行時(shí)的數(shù)據(jù)。
另外,當(dāng)運(yùn)行在Ali-tomcat時(shí),會(huì)顯示當(dāng)前tomcat的實(shí)時(shí)信息,如HTTP請(qǐng)求的qps, rt, 錯(cuò)誤數(shù), 線程池信息等等。

thread
通過thread命令可以查看當(dāng)前jvm進(jìn)程的線程詳情??梢圆榭淳€程的cpu使用時(shí)間占比,通過指定各種參數(shù)可以找出最忙的幾個(gè)線程,以及阻塞其他線程的線程。具體如何使用這里不多做介紹,大家可以去看arthas的官方文檔。
1 | 當(dāng)前最忙的前N個(gè)線程并打印堆棧 |

jvm
通過jvm命令直接輸出當(dāng)前jvm的各種信息。
getstatic
通過getstatic命令可以方便的查看類的靜態(tài)屬性。
1 | getstatic demo.MahtGame random |

sysprop和sysenv
通過sysprop可以查看所有的系統(tǒng)變量,也可以設(shè)置某個(gè)系統(tǒng)變量。
同理,通過sysenv可以查看所有的操作系統(tǒng)環(huán)境變量,也可以查看設(shè)置某個(gè)環(huán)境變量。
ognl
執(zhí)行ognl表達(dá)式,可執(zhí)行任意代碼
1 | Ognl @demo.MathGame@random |

動(dòng)態(tài)修改日志級(jí)別
通過ognl獲取對(duì)應(yīng)類上的logger對(duì)象實(shí)現(xiàn)類
sc命令來查看具體從哪個(gè)jar包里加載的,sc -d org.apache.log4j.Logger
通過ognl修改日志級(jí)別

更詳細(xì)的介紹:https://github.com/alibaba/arthas/issues/11
dump
將已加載類的字節(jié)碼dump到本地磁盤上。
jad
有時(shí)我們經(jīng)常會(huì)不確定線上或者測試環(huán)境的包是否是我們修改過的,這時(shí)候就可以通過jad反編譯來看下。
1 | 反編譯指定已加載類的源碼,jad demo.MathGame |

classloader
將 JVM 中所有的classloader的信息統(tǒng)計(jì)出來,并可以展示繼承樹,urls等。
redafine
該命令可以加載外部的.class文件,然后覆蓋 jvm已加載的類。注意,這個(gè)命令不一定都能覆蓋成功,如果添加了新的field,就不會(huì)加載成功。
關(guān)于redefine,arthas的github上有個(gè)非常經(jīng)典的userCase:
https://github.com/alibaba/arthas/issues/263
動(dòng)態(tài)更新代碼,不用重啟jvm

watch
讓你能方便的觀察到指定方法的調(diào)用情況。能觀察到的范圍為:返回值、拋出異常、入?yún)ⅲㄟ^編寫 OGNL 表達(dá)式進(jìn)行對(duì)應(yīng)變量的查看。
watch的使用姿勢比較豐富,可以在四個(gè)不同的場景觀察方法的執(zhí)行。比如方法調(diào)用之前、方法調(diào)用之后、方法異常之后、方法結(jié)束之后。默認(rèn)觀察的是方法結(jié)束之后。
如果觀察的是方法結(jié)束之后的場景,由于入?yún)⒖赡茉趫?zhí)行方法時(shí)被改變,所以此時(shí)輸出的可能不是真正的入?yún)ⅰR虼?,要看真正的入?yún)ⅲ捶椒ㄕ{(diào)用之前的,也就是加上-b的參數(shù)。
另外,使用-b參數(shù)觀察的話,則觀察不到方法返回的結(jié)果以及拋出的異常了。
| 參數(shù)名稱 | 參數(shù)說明 |
|---|---|
| class-pattern | 類名表達(dá)式匹配 |
| method-pattern | 方法名表達(dá)式匹配 |
| express | 觀察表達(dá)式 |
| condition-express | 條件表達(dá)式 |
| [b] | 在方法調(diào)用之前觀察 |
| [e] | 在方法異常之后觀察 |
| [s] | 在方法返回之后觀察 |
| [f] | 在方法結(jié)束之后(正常返回和異常返回)觀察 |
| [E] | 開啟正則表達(dá)式匹配,默認(rèn)為通配符匹配 |
| [x:] | 指定輸出結(jié)果的屬性遍歷深度,默認(rèn)為 1 |
| [n:] | 只執(zhí)行n次,默認(rèn)會(huì)不斷輸出,除非用戶按下cltr+c |
1 | # 觀察CommonTest的test方法 |
在填寫條件表達(dá)式時(shí)要注意一點(diǎn),條件表達(dá)式中的params默認(rèn)都是獲取的方法執(zhí)行完后的參數(shù)信息,比如入?yún)的屬性name方法執(zhí)行前是”hello”,在方法執(zhí)行后變成了”world”,那么條件表達(dá)式傳入’params[0].name=”hello”‘將不會(huì)輸出,只有填入’params[0].name=”hello”‘才可以匹配上。這點(diǎn)對(duì)于后面的trace、stack命令也是一樣的。
查看方法輸入?yún)?shù)/返回值/異常信息,watch demo.MathGame primeFactors “{params,returnObj}” -x 2
monitor
monitor命令可以監(jiān)控方法的執(zhí)行情況。比如調(diào)用成功次數(shù),失敗次數(shù),失敗率、平均執(zhí)行時(shí)間等等。默認(rèn)120秒輸出一次,也就是說,當(dāng)我們輸入monitor命令之后,每120秒就會(huì)輸出一次統(tǒng)計(jì)結(jié)果。
通過-c參數(shù)可以修改輸出頻率,支持通配符和正則表達(dá)式。
monitor -c 5 demo.MathGame primeFactors
trace
方法內(nèi)部調(diào)用路徑,并輸出方法路徑上的每個(gè)節(jié)點(diǎn)上耗時(shí),tt命令會(huì)記錄每次方法調(diào)用的各種信息。它和watch有些相似但是它能記錄下各個(gè)時(shí)間點(diǎn)的調(diào)用信息,之后隨時(shí)查看,甚至replay這次調(diào)用。
1 | trace demo.MathGame run |

tt
timetunnel,記錄下指定方法每次調(diào)用的入?yún)⒑头祷匦畔ⅲ⒛軐?duì)這些不同的時(shí)間下調(diào)用進(jìn)行觀測,同時(shí)可回放該方法調(diào)用
tt -t demo.MathGame primeFactors

直接重放請(qǐng)求參數(shù)
1 | 1.使用tt –t 類名次 方法名次 |
另外介紹幾個(gè)我經(jīng)常用的工具,jmap,jstat,jstack,dump這些基本的就不介紹了。
Tprofiler
TProfiler是一個(gè)可以在生產(chǎn)環(huán)境長期使用的性能分析工具.它同時(shí)支持剖析和采樣兩種方式,記錄方法執(zhí)行的時(shí)間和次數(shù),生成方法熱點(diǎn) 對(duì)象創(chuàng)建熱點(diǎn) 線程狀態(tài)分析等數(shù)據(jù),為查找系統(tǒng)性能瓶頸提供數(shù)據(jù)支持.
TProfiler在JVM啟動(dòng)時(shí)把時(shí)間采集程序注入到字節(jié)碼中,整個(gè)過程無需修改應(yīng)用源碼.運(yùn)行時(shí)會(huì)把數(shù)據(jù)寫到日志文件,一般情況下每小時(shí)輸出的日志小于50M.
show-busy-java-threads
查找cpu性能消耗過程
top命令找出消耗CPU高的Java進(jìn)程及其線程id:
開啟線程顯示模式(top -H,或是打開top后按H)
按CPU使用率排序(top缺省是按CPU使用降序,已經(jīng)合要求;打開top后按P可以顯式指定按CPU使用降序)
記下Java進(jìn)程id及其CPU高的線程id查看消耗CPU高的線程棧:
用進(jìn)程id作為參數(shù),jstack出有問題的Java進(jìn)程
手動(dòng)轉(zhuǎn)換線程id成十六進(jìn)制(可以用printf %x 1234)
在jstack輸出中查找十六進(jìn)制的線程id(可以用vim的查找功能/0x1234,或是grep 0x1234 -A 20)查看對(duì)應(yīng)的線程棧,分析問題
查問題時(shí),會(huì)要多次上面的操作以分析確定問題,這個(gè)過程太繁瑣太慢了。參考:https://blog.csdn.net/u013332124/article/details/84888074#11_redefine_157
