圖片看不到的話可以看我的CSDN的博客
https://blog.csdn.net/u013332124/article/details/84888074
Arthas
是Alibaba開源的Java診斷工具锨咙,采用命令行交互模式雹有,提供了豐富的功能,是排查jvm相關問題的利器犁享。
在逛github時,發(fā)現(xiàn)了這款利器塔逃,深入了解之后殿托,簡直驚為天人。下面先列舉一下它能做的一些事情:
- 提供性能看板冬念,包括線程、cpu牧挣、內(nèi)存等信息急前,并且會定時的刷新。
- 根據(jù)各種條件查看線程快照瀑构。比如找出cpu占用率最高的n個線程等
- 輸出jvm的各種信息裆针,如gc算法、jdk版本寺晌、ClassPath等
- 查看/設置sysprop和sysenv
- 查看某個類的靜態(tài)屬性世吨,也可以通過ognl語法執(zhí)行一些語句
- 查看已加載的類的詳細信息,比如這個類從哪個jar包加載的呻征。也可以查看類的方法的信息
- dump某個類的字節(jié)碼到指定目錄
- 直接反編譯指定的類
- 查看類加載器的一些信息
- 可以讓jvm重新加載某個類
- 監(jiān)控方法的執(zhí)行耘婚,同時可以獲取到執(zhí)行的入?yún)ⅰ⒊鰠⒁约皰伋龅漠惓?/li>
- 追蹤方法執(zhí)行的調(diào)用棧陆赋,以及各個方法的調(diào)用時間
下面我會對如何使用這些功能做一個簡單的介紹沐祷,也會加上一些自己對這些命令的理解嚷闭。有一些命令不會介紹的太詳細,因為官方文檔已經(jīng)寫的很棒了赖临,我沒必要再這復述胞锰。想深入了解的朋友也可以直接去看arthas的官方文檔,中文的兢榨,很容易閱讀胜蛉。
https://alibaba.github.io/arthas/install-detail.html
一、安裝和使用arthas
官方文檔:
https://alibaba.github.io/arthas/install-detail.html
安裝
直接通過`java -jar啟動:
wget https://alibaba.github.io/arthas/arthas-boot.jar
# 啟動后會自動下載響應的lib到 ~/.arthas 目錄下
java -jar arthas-boot.jar
或者直接下載arthas的壓縮包色乾,然后解壓:
unzip arthas-packaging-bin.zip
# 執(zhí)行
./as.sh
啟動asthas進程后,它會列出所有的jvm進程领突,并讓我們選擇要attach哪個進程暖璧。attach目標進程后,就進入athas的交互命令行了君旦,這時候就可以開始輸入arthas對應的命令使用了
卸載
rm -rf ~/.arthas/
二澎办、athas的各個命令
1. dashboard
進入當前系統(tǒng)的實時數(shù)據(jù)面板,按 ctrl+c 退出金砍。這個面板會實時刷新局蚀,其中包括線程信息、內(nèi)存信息恕稠、gc信息琅绅、還有一些運行時的數(shù)據(jù)。
另外鹅巍,當運行在Ali-tomcat時千扶,會顯示當前tomcat的實時信息,如HTTP請求的qps, rt, 錯誤數(shù), 線程池信息等等骆捧。
下面是arthas上面的一張demo圖
2. thread
通過thread命令可以查看當前jvm進程的線程詳情澎羞。可以查看線程的cpu使用時間占比敛苇,通過指定各種參數(shù)可以找出最忙的幾個線程妆绞,以及阻塞其他線程的線程。具體如何使用這里不多做介紹枫攀,大家可以去看arthas的官方文檔括饶。
3. jvm
通過jvm命令直接輸出當前jvm的各種信息。
4. sysprop和sysenv
通過sysprop可以查看所有的系統(tǒng)變量脓豪,也可以設置某個系統(tǒng)變量巷帝。
同理,通過sysenv可以查看所有的操作系統(tǒng)環(huán)境變量扫夜,也可以查看設置某個環(huán)境變量楞泼。
5. getstatic
通過該命令可以查看類的靜態(tài)屬性驰徊。不過查看類的靜態(tài)屬性ognl命令也可以做到,官方也比較推薦使用ognl表達式來做堕阔。
不過使用getstatic可以使用通配符來查看變量棍厂,好像用ognl不行。(也可能是我對ognl表達式了解還不夠)
# 查看CommonConstants類下的所有靜態(tài)屬性
getstatic *.CommonConstants *
6. ognl
通過ognl表達式來執(zhí)行一些語句超陆。
# 調(diào)用靜態(tài)函數(shù)
ognl '@java.lang.System@out.println("hello")'
# 輸出
null
# 獲取靜態(tài)類的靜態(tài)字段
ognl '@demo.MathGame@random'
# 輸出
@Random[
serialVersionUID=@Long[3905348978240129619],
seed=@AtomicLong[125451474443703],
multiplier=@Long[25214903917],
addend=@Long[11],
mask=@Long[281474976710655],
DOUBLE_UNIT=@Double[1.1102230246251565E-16],
BadBound=@String[bound must be positive],
BadRange=@String[bound must be greater than origin],
BadSize=@String[size must be non-negative],
seedUniquifier=@AtomicLong[-3282039941672302964],
nextNextGaussian=@Double[0.0],
haveNextNextGaussian=@Boolean[false],
serialPersistentFields=@ObjectStreamField[][isEmpty=false;size=3],
unsafe=@Unsafe[sun.misc.Unsafe@28ea5898],
seedOffset=@Long[24],
]
# 執(zhí)行多行表達式牺弹,賦值給臨時變量,返回一個List
ognl '#value1=@System@getProperty("java.home"),#value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
# 輸出
@ArrayList[
@String[/opt/java/8.0.181-zulu/jre],
@String[OpenJDK Runtime Environment],
]
ognl表達式在arthas中用的還是比較多的时呀,語法也比較簡單张漂。在后面的monitor、watch谨娜、trace航攒、stack等命令中都會排上用場。
關于ognl趴梢,這個userCase上有豐富的案例:
https://github.com/alibaba/arthas/issues/11
7. sc 和 sm
通過sc可以查看已加載類的相關信息漠畜,比如該類是從哪個jar包加載的,被哪個類加載器加載的坞靶,以及是否是接口等等憔狞。
sm查看已加載類的方法詳情。
8. dump
將已加載類的字節(jié)碼dump到本地磁盤上彰阴。
9. jad
反編譯已加載的類瘾敢。讓它變成可讀的形式。
有時我們經(jīng)常會不確定線上或者測試環(huán)境的包是否是我們修改過的硝枉,這時候就可以通過jad反編譯來看下廉丽。
10. classloader
將 JVM 中所有的classloader的信息統(tǒng)計出來,并可以展示繼承樹妻味,urls等正压。
11. redefine
該命令可以加載外部的.class
文件,然后覆蓋 jvm已加載的類责球。注意焦履,這個命令不一定都能覆蓋成功,如果添加了新的field雏逾,就不會加載成功嘉裤。
關于redefine,arthas的github上有個非常經(jīng)典的userCase:
https://github.com/alibaba/arthas/issues/263
大體就是作者遇到項目中的日志一直輸出[] [] [] No credential found
栖博,想要找到是哪個類輸出的屑宠。由于大多數(shù)日志框架輸出日志時都用到了StringBuilder,因此作者對StringBuilder的toString方法做了以下修改:
@Override
public String toString() {
// Create a copy, don't share the array
String result = new String(value, 0, count);
if(result.contains("No credential found")) {
System.err.println(result);
new Throwable().printStackTrace();
}
return result;
}
改完類之后再用redefine把修改后的StringBuilder加載進去仇让,這樣典奉,當后面繼續(xù)輸出[] [] [] No credential found
就可以知道到底是從哪里輸出的了躺翻。
12. monitor
monitor命令可以監(jiān)控方法的執(zhí)行情況。比如調(diào)用成功次數(shù)卫玖,失敗次數(shù)公你,失敗率、平均執(zhí)行時間等等假瞬。默認120秒輸出一次陕靠,也就是說,當我們輸入monitor命令之后脱茉,每120秒就會輸出一次統(tǒng)計結果剪芥。
通過-c參數(shù)可以修改輸出頻率,支持通配符和正則表達式琴许。
13. watch
讓你能方便的觀察到指定方法的調(diào)用情況粗俱。能觀察到的范圍為:返回值
、拋出異常
虚吟、入?yún)?/code>,通過編寫 OGNL 表達式進行對應變量的查看签财。
watch的使用姿勢比較豐富串慰,可以在四個不同的場景觀察方法的執(zhí)行。比如方法調(diào)用之前唱蒸、方法調(diào)用之后邦鲫、方法異常之后、方法結束之后神汹。默認觀察的是方法結束之后庆捺。
如果觀察的是方法結束之后的場景,由于入?yún)⒖赡茉趫?zhí)行方法時被改變屁魏,所以此時輸出的可能不是真正的入?yún)⑻弦浴R虼耍凑嬲娜雲(yún)⒚テ矗捶椒ㄕ{(diào)用之前的你画,也就是加上-b的參數(shù)。
另外桃漾,使用-b參數(shù)觀察的話坏匪,則觀察不到方法返回的結果以及拋出的異常了。
相關參數(shù):
參數(shù)名稱 | 參數(shù)說明 |
---|---|
class-pattern | 類名表達式匹配 |
method-pattern | 方法名表達式匹配 |
express | 觀察表達式 |
condition-express | 條件表達式 |
[b] | 在方法調(diào)用之前觀察 |
[e] | 在方法異常之后觀察 |
[s] | 在方法返回之后觀察 |
[f] | 在方法結束之后(正常返回和異常返回)觀察 |
[E] | 開啟正則表達式匹配撬统,默認為通配符匹配 |
[x:] | 指定輸出結果的屬性遍歷深度适滓,默認為 1 |
[n:] | 只執(zhí)行n次,默認會不斷輸出恋追,除非用戶按下cltr+c |
# 觀察CommonTest的test方法
# 輸出 入?yún)⑵炯!⒎祷亟Y果罚屋、拋出的異常 —— 輸出的內(nèi)容可以動態(tài)調(diào)整
# 后面跟著的是 條件表達式,表示耗時超過10ms才輸出
# -n 表示只執(zhí)行一次蕊苗,-x表示 入?yún)⒑头祷亟Y果的展開層次為5層
watch *.CommonTest test "{params,returnObj,throwExp}" '#cost>10' -x 5 -n 1
# 耗時大于10ms并且第一個參數(shù)等于1才輸出
watch *.CommonTest test "{params,returnObj,throwExp}" '#cost>10 && params[0]==1' -x 5 -n 1
# 第一個參數(shù)大于1 并且第二個參數(shù)等于hello才輸出
watch *.CommonTest test "{params,returnObj,throwExp}" 'params[0]>1 && params[1]=="hello"' -x 5 -n 1
# 第一個參數(shù)小于5或者第二個參數(shù)等于"world"就輸出
watch *.CommonTest test "{params,returnObj,throwExp}" 'params[0]<5 || params[1]=="wolrd"' -x 5 -n 1
# 第一個參數(shù)的name字段等于world時才輸出沿后。
# 由于在方法執(zhí)行過程中參數(shù)的name屬性可能發(fā)生改變,因此加上-b才能觀察到真正的入?yún)?watch -b *.CommonTest test "{params,returnObj,throwExp}" 'params[0].name=="wolrd"' -x 5 -n 1
# 由于同時指定了-s和-b朽砰,所以方法被調(diào)用一次尖滚,就會輸出2次結果(兩個場景分開輸出),分別是方法被調(diào)用前瞧柔,和返回之后
# 注意漆弄,這里如果-n只設置成1,那么只會輸出-b對應的輸出,-s對應的輸出由于沒有次數(shù)了就無法輸出了
watch *.CommonTest test '{params,returnObj,throwExp}' -x 5 -n 2 -s -b
在填寫條件表達式時要注意一點造锅,條件表達式中的params默認都是獲取的方法執(zhí)行完后的參數(shù)信息撼唾,比如入?yún)的屬性name方法執(zhí)行前是"hello",在方法執(zhí)行后變成了"world",那么條件表達式傳入'params[0].name="hello"'
將不會輸出,只有填入'params[0].name="hello"'
才可以匹配上哥蔚。這點對于后面的trace倒谷、stack命令也是一樣的。
14. trace
方法內(nèi)部調(diào)用路徑糙箍,并輸出方法路徑上的每個節(jié)點上耗時渤愁。但是該命令只能輸出一級調(diào)用的方法耗時,往下的就不會輸出了深夯。比如我定義了一個類Test
public class Test{
public void a(){
b();
}
public void b(){
c();
}
public void c(){
//...
}
}
當我要觀察Test a
時抖格,trace命令只會輸出b()
的耗時,而不會輸出c()
的耗時咕晋。因為對方法a來說雹拄,只有b()
是一級調(diào)用。
trace命令也可以使用條件表達式掌呜,來過濾一些不想要的輸出
# 方法耗時大于100時才輸出滓玖,且只輸出1.其他條件過濾的語法可以看watch命令的demo
trace *.Common* test '#cost>100' -n 1
友情提醒下,
trace
在執(zhí)行的過程中本身是會有一定的性能開銷质蕉,在統(tǒng)計的報告中并未像 JProfiler 一樣預先減去其自身的統(tǒng)計開銷呢撞。所以這統(tǒng)計出來有些許的不準,渲染路徑上調(diào)用的類饰剥、方法越多殊霞,性能偏差越大。但還是能讓你看清一些事情的汰蓉。這里存在一個統(tǒng)計不準確的問題绷蹲,就是所有方法耗時加起來可能會小于該監(jiān)測方法的總耗時,這個是由于 Arthas 本身的邏輯會有一定的耗時
15. stack
輸出當前方法被調(diào)用的調(diào)用路徑。用法和trace差不多
# 方法耗時大于100時才輸出祝钢,且只輸出1.其他條件過濾的語法可以看watch命令的demo
stack *.Common* test '#cost>100' -n 1
16. tt
tt命令會記錄每次方法調(diào)用的各種信息比规。它和watch有些相似但是它能記錄下各個時間點的調(diào)用信息,之后隨時查看拦英,甚至replay這次調(diào)用蜒什。
從上圖中我們可以看到,通過-i
參數(shù)我們可以直接查看之前某次調(diào)用的詳細信息疤估。
# 指定第一個參數(shù)的mobile字段等于13989838402
tt -t *.CommonTest test params[0].mobile=="13989838402"
# 把之前記錄的那些調(diào)用都輸出來
tt -l
#
replay某次調(diào)用:
# replay時間片的index=1002這次調(diào)用
tt -i 1002 -p
使用tt進行方法replay時灾常,要注意2點
ThreadLocal 信息丟失
很多框架偷偷的將一些環(huán)境變量信息塞到了發(fā)起調(diào)用線程的 ThreadLocal 中,由于調(diào)用線程發(fā)生了變化铃拇,這些 ThreadLocal 線程信息無法通過 Arthas 保存钞瀑,所以這些信息將會丟失。
一些常見的 CASE 比如:鷹眼的 TraceId 等慷荔。
引用的對象
需要強調(diào)的是雕什,
tt
命令是將當前環(huán)境的對象引用保存起來,但僅僅也只能保存一個引用而已显晶。如果方法內(nèi)部對入?yún)⑦M行了變更贷岸,或者返回的對象經(jīng)過了后續(xù)的處理,那么在tt
查看的時候將無法看到當時最準確的值磷雇。這也是為什么watch
命令存在的意義凰盔。
17. options
options用來打開關閉某些功能:
名稱 | 默認值 | 描述 |
---|---|---|
unsafe | false | 是否支持對系統(tǒng)級別的類進行增強,打開該開關可能導致把JVM搞掛倦春,請慎重選擇! |
dump | false | 是否支持被增強了的類dump到外部文件中落剪,如果打開開關睁本,class文件會被dump到/${application dir}/arthas-class-dump/ 目錄下,具體位置詳見控制臺輸出 |
batch-re-transform | true | 是否支持批量對匹配到的類執(zhí)行retransform操作 |
json-format | false | 是否支持json化的輸出 |
disable-sub-class | false | 是否禁用子類匹配忠怖,默認在匹配目標類的時候會默認匹配到其子類呢堰,如果想精確匹配,可以關閉此開關 |
debug-for-asm | false | 打印ASM相關的調(diào)試信息 |
save-result | false | 是否打開執(zhí)行結果存日志功能凡泣,打開之后所有命令的運行結果都將保存到/home/admin/logs/arthas/arthas.log 中 |
job-timeout | 1d | 異步后臺任務的默認超時時間枉疼,超過這個時間,任務自動停止鞋拟;比如設置 1d, 2h, 3m, 25s骂维,分別代表天、小時贺纲、分航闺、秒 |
$ options save-result true
NAME BEFORE-VALUE AFTER-VALUE
----------------------------------------
save-result false true
18. help
通過help命令可以查看某個命令的使用詳情
help tt
help stack
...
三、遇到的一些問題
在使用arthas的過程中,會遇到一些問題潦刃,這里做個記錄侮措。
1. attach錯進程
在我們要去sc、jad某個類乖杠,發(fā)現(xiàn)arthas提示類沒加載分扎,但是我們很確定類已經(jīng)加載了。這時候就要檢查arthas attach的進程是不是我們目標的進程胧洒。在進入arthas交互界面時會輸出一些信息畏吓,里面就有真正attach的進程id:
在上圖我們可以看到,明明我選擇的是要attach 37179這個進程略荡,但實際arthas attach的是26792這個進程庵佣。這是由于我們之前用arthas attach過26792這個進程,并且沒有執(zhí)行 shutdown命令導致的汛兜。也就是說執(zhí)行exit或者quit只會退出交互界面巴粪,不會關閉attach的arthas進程。
解決辦法
再次進入arthas交互界面粥谬,執(zhí)行 shutdown
命令肛根,之后重新attach新的進程即可。
四漏策、總結
在以前派哲,我們排查線上jvm進程時,用的命令無非就是jps掺喻、jstack芭届、jmap、jstat感耙、jhat
這些jvm命令褂乍。但是這些命令能做的事都比較有限。有了arthas即硼,我們可以在線上進行更加豐富的操作逃片,對我們排查問題有很大的幫助。
另外只酥,想學習arthas的話褥实,建議不要只看文檔,最好自己手動把所有命令都試一遍裂允,印象會比較深刻损离,下次遇到類似的問題也可以及時想到這個工具。