Arthas 是阿里開源的 Java 診斷工具,相比 JDK 內(nèi)置的診斷工具,要更人性化,并且功能強(qiáng)大,可以實(shí)現(xiàn)許多問(wèn)題的一鍵定位,是我用到的最方便的診斷工具。
下載和安裝見官網(wǎng) https://arthas.aliyun.com/doc/profiler.html
下面記錄一些我工作中常用到的指令
1. dashboard : 展示當(dāng)前進(jìn)程信息
主要是兩部分: 線程信息(按照cpu使用率倒序) 和 內(nèi)存信息
可以快速發(fā)現(xiàn) 當(dāng)前前幾個(gè)cpu使用高的線程

2. thread :查看當(dāng)前 JVM 的線程堆棧信息

thread : 獲取當(dāng)前所有線程 (按照cpu使用率倒序)
thread 4602 threathe : 獲取指定線程堆棧信息
thread -n 8 : 當(dāng)前最忙的前8個(gè)線程并打印堆棧
thread -n 8 -i 3000 : 列出3s內(nèi)最忙的8個(gè)線程并打印堆棧(-i 為采樣時(shí)間)
thread -b :找出當(dāng)前阻塞其他線程的線程
有時(shí)候我們發(fā)現(xiàn)應(yīng)用卡住了, 通常是由于某個(gè)線程拿住了某個(gè)鎖, 并且其他線程都在等待這把鎖造成的。
為了排查這類問(wèn)題, arthas提供了thread -b, 一鍵找出那個(gè)罪魁禍?zhǔn)住?注意, 目前只支持找出synchronized關(guān)鍵字阻塞住的線程, 如果是java.util.concurrent.Lock, 目前還不支持。
3. heapdump : dump java heap, 類似jmap命令的heap dump功能。
heapdump /tmp/dump.hprof : dump到指定文件
heapdump --live /tmp/dump.hprof : 只dump live對(duì)象
下載dump文件后,可以使用MAT查看,MAT官網(wǎng)下載: https://www.eclipse.org/mat/previousReleases.php ,主要要選擇和本地一樣的jvm版本,不然啟動(dòng)會(huì)失敗。
使用MAT 分析 OOM 大內(nèi)存問(wèn)題,過(guò)程如下,是截取別的文章的過(guò)程。





4. watch: 觀察到指定函數(shù)的調(diào)用情況。能觀察到的范圍為:返回值、拋出異常、入?yún)?/h1>
這個(gè)用于觀察未打印日志的方法特別好用,包括入?yún)?返參 和 異常
watch xxx.xxx.ABTestRecordServiceImpl doABTestWithLayer "{params,returnObj}" -x 3 -n 2
-x 指定輸出結(jié)果的屬性遍歷深度,默認(rèn)為 1,最大值是4 ,
如果入?yún)⒑统鰠⑹菍傩陨疃炔灰粯樱ㄍǔJ遣灰粯拥模雲(yún)⒕褪且粋€(gè)對(duì)象,出差會(huì)統(tǒng)一被一個(gè)Result包裹),需要分別設(shè)置才能觀察到
params,returnObj : 指的是入?yún)⒑头祬?,固定?throwExp :觀察異常信息 watch demo.MathGame primeFactors "{params,throwExp}" -e -x 2
-b : 在函數(shù)調(diào)用之前觀察入?yún)?,因?yàn)槿雲(yún)⒂锌赡茉趫?zhí)行被修改
-e :在函數(shù)異常之后觀察
-s : 在函數(shù)返回之后觀察,因?yàn)槿雲(yún)⒂锌赡茉趫?zhí)行被修改
-n : 表示執(zhí)行次數(shù),即只打印n次,對(duì)于頻繁調(diào)用來(lái)說(shuō),不設(shè)置的話,會(huì)打印很多
watch xxx.xxx.ABTestRecordServiceImpl doABTestWithLayer "{params,returnObj}" -x 3 -n 2 '#cost>100'
'#cost>100' : 按照耗時(shí)過(guò)濾,在很多方法中都可以通用,表示只打印執(zhí)行超過(guò)100ms的方法

實(shí)例應(yīng)用:生產(chǎn)上一個(gè)已廢棄的接口突然被調(diào)用,報(bào)了一個(gè)異常,由于是老接口,我們并沒有catch捕獲,導(dǎo)致我們的日志filter沒能打印出入?yún)?lái),我們想根據(jù)入?yún)?lái)定位是哪里在調(diào)用,是誰(shuí)在調(diào)用,這時(shí)候就可以用 watch 來(lái)獲取方法的調(diào)用入?yún)ⅰ?br> 發(fā)現(xiàn)是運(yùn)營(yíng)突然上線了一個(gè)很久都沒用的活動(dòng)導(dǎo)致的,我們直接下線就可以了。
5. trace: 渲染和統(tǒng)計(jì)整個(gè)調(diào)用鏈路上的所有性能開銷和追蹤調(diào)用鏈路。
trace命令只會(huì)匹配到的函數(shù)里的子調(diào)用,并不會(huì)向下trace多層。因?yàn)閠race是代價(jià)比較貴的,多層trace可能會(huì)導(dǎo)致最終要trace的類和函數(shù)非常多。
trace xxx.DiscountCXOpenServiceImpl aaaRespCXDTOList '#cost > 50' -n 1
-n : 打印次數(shù)
'#cost>100' : 方法執(zhí)行耗時(shí)
默認(rèn)情況下,trace不會(huì)包含jdk里的函數(shù)調(diào)用,如果希望trace jdk里的函數(shù),需要顯式設(shè)置 --skipJDKMethod false。

6. stack:輸出當(dāng)前方法被調(diào)用的調(diào)用路徑
如果一個(gè)方法被很多地方調(diào)用了,但是我們又不知道是被哪里調(diào)用時(shí),可用這個(gè)命令查詢
stack xxx.DiscountCacheUtil getStrategyCachaaa -n 1

7. profiler:生成應(yīng)用熱點(diǎn)的火焰圖。
profiler start : 啟動(dòng)
啟動(dòng)時(shí),默認(rèn)采樣的是cpu,可以通過(guò) --event 來(lái)指定跟蹤事件(cpu , alloc , lock )
profiler status :查看狀態(tài)
profiler stop : 停止
停止時(shí),默認(rèn)生成html格式文件,可以通過(guò) --file 指定生成路徑和svg類型


從上面可以看出來(lái)耗時(shí)主要是在json parseArray格式化上。 橫向長(zhǎng)度越長(zhǎng),說(shuō)明耗時(shí)越久
火焰圖說(shuō)明:火焰圖是基于 perf 結(jié)果產(chǎn)生的 SVG 圖片,用來(lái)展示 CPU 的調(diào)用棧。
y 軸表示調(diào)用棧,每一層都是一個(gè)函數(shù)。調(diào)用棧越深,火焰就越高,頂部就是正在執(zhí)行的函數(shù),下方都是它的父函數(shù)。
x 軸表示抽樣數(shù),如果一個(gè)函數(shù)在 x 軸占據(jù)的寬度越寬,就表示它被抽到的次數(shù)多,即執(zhí)行的時(shí)間長(zhǎng)。注意,x 軸不代表時(shí)間,而是所有的調(diào)用棧合并后,按字母順序排列的。
火焰圖就是看頂層的哪個(gè)函數(shù)占據(jù)的寬度最大。只要有“平頂”(plateaus),就表示該函數(shù)可能存在性能問(wèn)題。
顏色沒有特殊含義,因?yàn)榛鹧鎴D表示的是 CPU 的繁忙程度,所以一般選擇暖色調(diào)。
默認(rèn)生成的是html格式,只是html格式找東西不方便

8. Arthas線上常用場(chǎng)景
8.1 CPU過(guò)高問(wèn)題
現(xiàn)象描述:運(yùn)維突然打電話說(shuō)是生成線上節(jié)點(diǎn)cpu高達(dá)80%,而且還在增加,但是最新的一個(gè)版本并沒有什么復(fù)雜的功能上線,而且日志在報(bào)一個(gè)批量插入主鍵沖突的異常,但是這個(gè)批量插入的功能已經(jīng)上線很久,并且在很多地方都有用到,所以不能定位到是哪里的問(wèn)題,這時(shí)候可以用arthas來(lái)查找問(wèn)題原因
//dashboard 命令用于整體展示進(jìn)程所有線程、內(nèi)存、GC 等情況
dashboard -- 查看發(fā)現(xiàn)cpu高 不是GC引起的,占用CPU較多的線程只有一個(gè)
thread -n 6 -- 查看最繁忙的線程在執(zhí)行的線程堆棧信息,然后可以直接定位具體代碼行
在這個(gè)案例中,我們通過(guò) Arthas 工具排查了高 CPU 的問(wèn)題:
- 首先,通過(guò) dashboard + thread 命令,基本可以在幾秒鐘內(nèi)一鍵定位問(wèn)題,找出消耗 CPU 最多的線程和方法棧;
- 然后,直接 jad 反編譯相關(guān)代碼,來(lái)確認(rèn)根因;
- 此外,如果調(diào)用入?yún)⒉幻鞔_的話,可以使用 watch 觀察方法入?yún)ⅲ⒏鶕?jù)方法執(zhí)行時(shí)間來(lái)過(guò)濾慢請(qǐng)求的入?yún)ⅰ?/li>
8.2 TPS過(guò)低問(wèn)題
現(xiàn)象描述:新上線一個(gè)高并發(fā)的復(fù)雜業(yè)務(wù)接口,壓測(cè)的時(shí)候發(fā)現(xiàn)TPS只有50,明顯是太低了,需要找出耗時(shí)較長(zhǎng)的地方加以優(yōu)化。
trace xxx.DiscountCXOpenServiceImpl aaaRespCXDTOList '#cost > 100' -n 1
由于trace命令只會(huì)匹配到的函數(shù)里的子調(diào)用,并不會(huì)向下trace多層,而這個(gè)業(yè)務(wù)接口很復(fù)雜,所以一層層找下去的話,太過(guò)于麻煩,所以用profiler 生成應(yīng)用熱點(diǎn)的火焰圖來(lái)查找比較好,
profiler start --event alloc
//等待10s
profiler stop --file /app/deploy/logs/profiler.svg

從上面的圖中可以看出最終耗時(shí)比較久的竟然是 fastjson 的 parseArray方法,然后找到具體調(diào)用的地方后,
發(fā)現(xiàn)是因?yàn)閮?nèi)部緩存的原因,由于內(nèi)部緩存用的公共方法,value為Stiring,所以每次getValue后,都需要parseArray 轉(zhuǎn)化為L(zhǎng)ist<Bean> ,之所以使用內(nèi)部緩存就是因?yàn)関alue值太大,存redis時(shí)會(huì)造成網(wǎng)卡帶寬不夠,所以我們需要修改為自定義內(nèi)部緩存,這樣就可以避免 parseArray 方法,從而提高性能了。
private static ConcurrentHashMap<String, ConcurrentHashMap> cache = new ConcurrentHashMap<>();
這里其實(shí)還有一個(gè)問(wèn)題,這種大對(duì)象頻繁反序列化除了影響性能,還會(huì)每次都生成新的對(duì)象,導(dǎo)致在高并發(fā)下,內(nèi)存也飆升。
8.3 排查線程阻塞問(wèn)題
1、thread篩選所有阻塞狀態(tài)的線程。
2、根據(jù)線程名稱定位具體的業(yè)務(wù)模塊,再選中該業(yè)務(wù)中的一條線程查看堆棧信息。
3、根據(jù)線程堆棧信息定位導(dǎo)致阻塞的具體方法,再利用stack命令查看方法堆棧信息。
4、利用jad工具反編譯源碼,分析業(yè)務(wù)邏輯代碼并改善。
8.4 排查死鎖問(wèn)題
- 利用Arthas來(lái)檢測(cè)死鎖特別簡(jiǎn)單,只需要執(zhí)行一行命令thread -b即可。
8.5 排查方法執(zhí)行過(guò)慢問(wèn)題
1、通過(guò)trace命令排查方法執(zhí)行速度,trace xx類 xx方法 '#cost>50ms',觀測(cè)執(zhí)行時(shí)間大于50ms的該方法的調(diào)用信息。
2、可以結(jié)合正則表達(dá)式,同時(shí)排查多個(gè)類、多個(gè)方法,trace -E ClassA|ClassB method1|method2|method3。
8.6 動(dòng)態(tài)修改線上代碼
上線之后,發(fā)現(xiàn)代碼有一處小地方存在邏輯錯(cuò)誤需要更改,可以直接線上修改,而不用重啟。
- 1、通過(guò)jad將要修改的類反編譯為.java文件,輸出到指定目錄。
- 2、本地糾正.java文件后,通過(guò)mc命令重新編譯.java文件。
- 4、通過(guò)retransform命令將剛編譯的.class文件再次加載到JVM中。
這種已經(jīng)很少使用了,僅限于不能重啟的情況下,通常還是直接發(fā)版解決的。