arthas學習圖文記錄

pluto_charon發表於2022-05-17

Arthas 是阿里開源的 Java 診斷工具。線上排查問題,無需重啟;動態跟蹤 Java 程式碼;實時監控 JVM 狀態。Arthas 支援 JDK 6+,支援 Linux/Mac/Windows,採用命令列互動模式,同時提供豐富的 Tab 自動補全功能,進一步方便進行問題的定位和診斷。

Arthas可以通過簡單的命令互動模式,接入執行的JVM,快速定位和診斷線上程式執行的問題。在不重啟服務的情況下,實時,動態的修改相關程式碼,並實時生效,具體工作原理如下:

  1. 連線JVM,通過attach機制,通過attach pid連線正在執行的JVM
  2. 檢視及修改JVM位元組碼,通過instrument技術對執行中的JVM附加或修改位元組碼來實現增強的邏輯

Arthas的執行過程如下:Arthas底層呼叫rt.jar包的ManagementFactory獲取整個JVM內部資訊,通過命令整合與後端互動,執行,返回結果,整個工程簡單清晰,容易上手。

arthas-demo入門

可以使用阿里雲給的基礎教程地址練習: https://arthas.aliyun.com/doc/arthas-tutorials.html?language=cn&id=arthas-basics

在這裡,我使用自己的伺服器,跟著基礎教程做入門聯絡。

1.下載math-game.jar,再用java -jar命令啟動

[root@localhost arthas]# wget https://arthas.aliyun.com/math-game.jar
[root@localhost arthas]# java -jar arthas-boot.jar

2.新開Terminal ,下載arthas-boot.jar,再用java -jar命令啟動

[root@localhost arthas]# wget https://arthas.aliyun.com/arthas-boot.jar
[root@localhost arthas]# java -jar arthas-boot.jar

arthas-bootArthas的啟動程式,它啟動後,會列出所有的Java程式,使用者可以選擇需要診斷的目標程式。

選擇第一個程式,輸入 1 (math-game這個程式對應的就是1),再Enter/回車

Attach成功之後,會列印Arthas LOGO。輸入 help 可以獲取到更多的幫助資訊。

3. dashboard 命令可以檢視當前系統的實時資料皮膚

資料說明:

  • ID:java級別的執行緒ID,注意這個ID不能跟jstack中的nativeID一一對應
  • NAME:執行緒名
  • GROUP:執行緒組名
  • PRIORI:執行緒的優先順序,1~10之間的數字,越大表示優先順序越高
  • STATE:執行緒的狀態
  • %CPU:執行緒的CPU使用率,比如取樣間隔1000ms,某個執行緒的增量cpu時間為100ms,則cpu的使用率為100/1000=10%
  • DELTA_TIME:上次取樣之後執行緒執行增量cpu時間,資料格式為秒
  • TIME:執行緒執行總CPU時間,資料格式為 分:秒
  • INTERRUPTED:執行緒當前的中斷位狀態
  • DAEMON:是否是daemon(後臺)執行緒

4. thread 命令會列印執行緒ID 1的棧

還可以通過thread 1 | grep 'main(' 命令來查詢main class:

引數說明:

引數名稱 引數說明
id 執行緒id
[ n: ] 指定最忙的前N個執行緒並列印堆疊
[ b ] 找出當前阻塞其他執行緒的執行緒(目前只支援找出synchronized關鍵字阻塞住的執行緒, 如果是java.util.concurrent.Lock, 目前還不支援。)
[ i ] 指定cpu佔比統計的取樣間隔,單位為毫秒
[ --all ] 顯示所有匹配的執行緒
[ --state ] 檢視指定狀態的執行緒,如: thread --state WAITING

5. sc命令來查詢JVM裡已載入的類

sc為“Search-Class” 的簡寫,能搜尋出所有已經載入到 JVM 中的 Class 資訊。

引數說明:

引數名稱 引數說明
class-pattern 類名錶達式匹配,支援全限定名。如:demo.MathGame,也支援demo/MathGame
method-pattern 方法名錶達式匹配
[ d ] 輸出當前類的詳細資訊,包括這個類所載入的原始檔案來源,類的宣告,載入的ClassLoader等詳細資訊;如果一個類被多個ClassLoader所載入,則會出現多次
[ E ] 開啟正規表示式匹配,預設為萬用字元匹配
[ f ] 輸出當前類的成員變數資訊(需要配合引數 -d一起使用)
[ x: ] 指定輸出靜態變數時屬性的遍歷深度,預設為0,直接使用toString輸出
[ c: ] 指定class的ClassLoader的hashcode
[ classLoaderClass: ] 指定執行表示式的ClassLoader的class name
[ n: ] 具有詳細資訊的匹配類的最大數量,預設為100

sm為“Search-Method” 的簡寫,查詢某個類下所有的方法,與sc的功能類似,這裡就不詳細介紹了。

6. jad命令來反編譯程式碼

還可以反編譯指定的函式

反編譯時只顯示原始碼

預設情況下,反編譯結果裡會帶有ClassLoader資訊,通過--source-only選項,可以只列印原始碼。這樣就會清爽很多。

7. watch方法執行資料觀測

watch命令可以檢視函式的引數/返回值/異常資訊,通過編寫 OGNL 表示式進行對應變數的檢視。

從上面的結果裡,說明函式被執行了兩次,第一次結果是 location=AtExceptionExit,說明函式丟擲了異常,因此returnObj是null;第二次結果是location=AtExit,說明函式正常返回,因此可以看到returnObj的結果是一個ArrayList。

引數說明:

引數名稱 引數說明
class-pattern 類名錶達式匹配
method-pattern 方法名錶達式匹配
express 觀察表示式,預設值為:{params,target,returnObj},單個值可以不用加 {},多個值需要加
condition-express 條件表示式,不能加 {},可以使用逗號分隔子表示式,取表示式最後一個值來判斷
[ b ] 在方法呼叫之前觀察,預設關閉,由於觀察事件點是在方法呼叫前,此時返回值或異常均不存在,params代表方法入參
[ e ] 在方法異常之後觀察,預設關閉,params代表方法出參
[ s ] 在方法返回之後觀察,預設關閉,params代表方法出參
[ f ] 在方法結束之後(正常返回和異常返回)觀察,預設開啟,params代表方法出參
[ E ] 開啟正規表示式匹配,預設為萬用字元匹配
[ x: ] 指定輸出結果的屬性遍歷深度,預設為1
[ #cost ] 監控耗時

條件表示式的例子:

下面這個例子表示只有引數小於0的呼叫才會響應。

異常資訊的例子:

-e 表示丟擲異常時才觸發

express中,表示異常資訊的變數時throwExp

8. vmtool命令,可以搜尋記憶體物件

vmtool 利用JVMTI介面,實現查詢記憶體物件,強制GC等功能。

引數說明:

引數名稱 引數說明
--action getInstances 返回結果繫結到 instances變數上,它是陣列。
--className 指定類名(完成路徑),支援 java.lang.String,也支援java/lang/String
[ --limit ] 限制返回值數量,避免獲取超大資料時對JVM造成壓力。預設值是10。
[ -x ] 指定返回結果展開層數,預設為1
[ c: ] 指定class的ClassLoader的hashcode(通過sc命令找到載入class的classLoader)
[ classLoaderClass: ] 指定執行表示式的ClassLoader的class name

強制GC的命令:vmtool --action forceGc

9. 退出Arthas

exit 或者 quit 命令可以退出Arthas。退出Arthas之後,還可以再次用 java -jar arthas-boot.jar 來連線。

10. 徹底退出Arthas

exit/quit命令只是退出當前session,arthas server還在目標程式中執行。

想完全退出Arthas,可以執行 stop 命令。

Arthas的其他重點使用功能

1. mc 記憶體編譯器(Memory Compiler/記憶體編譯器)

Memory Compiler/記憶體編譯器,編譯.java檔案生成.class。通過 -c / --classLoaderClass 引數指定classLoader,-d 引數指定輸出目錄;編譯生成.class檔案之後,可以結合retransform 命令實現熱更新程式碼。retransform的限制:1.不允許新增加field和method 2.正在跑的函式,沒有退出不能生效

這裡還是使用arthas的提供的教程:https://arthas.aliyun.com/doc/arthas-tutorials.html?language=cn&id=command-mc-retransform

1.由於正在跑的函式沒有退出時不生效的,所以上面的math-game的demo就不能使用了,所以得下載另一個demo(一個簡單的spring-boot應用)。

[root@localhost arthas]# wget https://raw.githubusercontent.com/hengyunabc/spring-boot-inside/master/demo-arthas-spring-boot/demo-arthas-spring-boot.jar
[root@localhost arthas]# java -jar demo-arthas-spring-boot.jar 

2.新開一個Terminal;訪問下面這個路徑,可以看到報錯(500異常)了。

3.啟動arthas-boot應用。

4.反編譯程式碼可以看到,當id小於1是,就會丟擲異常。

在這裡,我們修改檔案,想讓id小於1時還是能正常返回,不丟擲異常。

5.jad反編譯UserController,將結果儲存在/tmp/UserController.java資料夾裡。

[arthas@1645]$ jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java

6.通過vim /tmp/UserController.java編輯java類。

7.通過sc查詢載入UserContoller的ClassLoader,也可以在jad時顯示原始碼,裡面也有classLoader的資訊。

以下三個命令任意一個都可以。可以看到,這個java類是由LaunchedURLClassLoader@1be6f5c3 這個類載入器載入的。

8.通過mc命令編譯,同時指定--classLoaderClass引數指定ClassLoader:

[arthas@1645]$ mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/UserController.java -d /tmp

也可以通過-c 引數指定ClassLoaderHash:

[arthas@1645]$ mc --c 1be6f5c3 /tmp/UserController.java -d /tmp

可以看到,在tmp資料夾下,根據UserController的類的全路徑編譯了一個class檔案:

9.retransform 命令中西載入新編譯好的UserContoller.class類:

10.重新編譯檔案可以看到,程式碼已經替換成最新的了:

11.訪問:

顯示已經替換過的類:retransform -l

恢復修改前的程式碼,清楚指定的類:retransform -d 1

清楚所有的:retransform --deleteAll

2.trace 方法內部呼叫路徑,並輸出方法路徑上的每個節點上耗時

可以觀察方法執行的時候哪個子呼叫比較慢

[arthas@1645]$ trace ClassName methodName

trace 命令能主動搜尋 class-patternmethod-pattern 對應的方法呼叫路徑,渲染和統計整個呼叫鏈路上的所有效能開銷和追蹤呼叫鏈路。在進行效能調優的時候十分有效。

引數說明:

引數名稱 引數說明
class-pattern 類名錶達式匹配
method-pattern 方法名錶達式匹配
express 觀察表示式,預設值為:{params,target,returnObj},單個值可以不用加 {},多個值需要加
condition-express 條件表示式
[ E ] 開啟正規表示式匹配,預設為萬用字元匹配
[ n: ] 命令執行次數
[ #cost ] 方法執行耗時
[ --skipJDKMethod ] 跳過jdk方法,預設為true

不跳過JDK方法:

只展示耗時大於1ms的呼叫路徑:

動態trace:

從上圖中可以看到,primeFactors的方法耗時最長,如果想深入primeFactors方法,可以開啟一個新的終端,使用telnet localhost 3658 連線上arthas,在trace primeFactors時指定listenerId。

這時終端2列印的結果,說明已經增強了一個函式:Affect(class count: 1 , method count: 1),但不再列印更多的結果。

再檢視終端1,可以發現trace的結果增加了一層,列印了primeFactors函式裡的內容:

注意 --listenerId指定的id在前一條命令的輸出中可以看到。

3.stack 檢視某個函式的呼叫堆疊路徑

很多時候,在一個方法被執行時,方法的執行路徑非常多,或者根本就不著調這個方法時從哪裡被執行的,就可以使用stack命令。此命令和trace命令結構類似。

[arthas@1645]$ stack demo.MathGame primeFactors

引數說明:

引數名稱 引數說明
class-pattern 類名錶達式匹配
method-pattern 方法名錶達式匹配
express 觀察表示式,預設值為:{params,target,returnObj},單個值可以不用加 {},多個值需要加
condition-express 條件表示式
[ E ] 開啟正規表示式匹配,預設為萬用字元匹配
[ n: ] 命令執行次數

4.tt 命令

tt是TimeTunnel 的縮寫,tt命令記錄方法執行資料的時空隧道,記錄下指定方法每次呼叫的入參和返回資訊,並能對這些不同的時間下呼叫進行觀測。

watch 雖然很方便和靈活,但需要提前想清楚觀察表示式的拼寫,這對排查問題而言要求太高,因為很多時候我們並不清楚問題出自於何方,只能靠蛛絲馬跡進行猜測。

這個時候如果能記錄下當時方法呼叫的所有入參和返回值、丟擲的異常會對整個問題的思考與判斷非常有幫助。

引數說明:

引數名稱 引數說明
-t 記錄下類對應的方法的每次執行情況
class-pattern 類名錶達式匹配
method-pattern 方法名錶達式匹配
[ n: ] 命令執行次數
condition-express 條件表示式

表格欄位說明:

表格欄位 欄位解釋
index 時間片段記錄編號,每一個編號代表著一次呼叫,後續tt還有很多命令都是基於此編號指定記錄操作,非常重要。
timestamp 方法執行的本機時間,記錄了這個時間片段所發生的本機時間
cost(ms) 方法執行的耗時
is-ret 方法是否以正常返回的形式結束
is-exp 方法是否以拋異常的形式結束
object 執行物件的hashCode(),注意,曾經有人誤認為是物件在JVM中的記憶體地址,但很遺憾他不是。但他能幫助你簡單的標記當前執行方法的類實體
class 執行的類名
method 執行的方法名

檢索呼叫記錄:

tt -l 檢索所有的呼叫記錄:

篩選出 primeFactors 方法的呼叫資訊:

通過 -i 引數後邊跟著對應的 INDEX 編號檢視到他的詳細資訊:

重做一次呼叫:

tt 命令由於儲存了當時呼叫的所有現場資訊,所以我們可以自己主動對一個 INDEX 編號的時間片自主發起一次呼叫。此時需要使用 -p 引數。通過 --replay-times 指定 呼叫次數,通過 --replay-interval 指定多次呼叫間隔(單位ms, 預設1000ms)

需要強調的點

  1. ThreadLocal 資訊丟失

    很多框架偷偷的將一些環境變數資訊塞到了發起呼叫執行緒的 ThreadLocal 中,由於呼叫執行緒發生了變化,這些 ThreadLocal 執行緒資訊無法通過 Arthas 儲存,所以這些資訊將會丟失。

  2. 引用的物件

    需要強調的是,tt 命令是將當前環境的物件引用儲存起來,但僅僅也只能儲存一個引用而已。如果方法內部對入參進行了變更,或者返回的物件經過了後續的處理,那麼在 tt 檢視的時候將無法看到當時最準確的值。這也是為什麼 watch 命令存在的意義。

5.monitor 方法執行監控

對匹配的class-pattern / method-pattern的類、方法的呼叫進行監控。monitor命令是一個非實時返回的命令(並不是輸入之後立即返回,而是不斷的等待目標java程式返回資訊)。

引數說明:

引數名稱 引數說明
class-pattern 類名錶達式匹配
method-pattern 方法名錶達式匹配
[ E ] 開啟正規表示式匹配,預設為萬用字元匹配
[ c: ] 統計週期,預設值為120秒,是一個整型的引數值

監控項說明:

監控項 timestamp class method total success tail rt fail-rate
說明 時間戳 java類 方法 呼叫次數 成功次數 失敗次數 平均rt 失敗率

5.target-ip

target-ip 為指定繫結的IP,如果不指定IP,Arthas只listen 127.0.0.1,所以如果想從遠端連線,則可以使用 --target-ip引數指定listen的IP。

java -jar arthas-boot.jar --target-ip IP

繫結遠端訪問IP後,可以在通過telnet 或者http的方式遠端連線Arthas進行問題排查

還有更多的功能請檢視arthas的官方文件。