Arthas 是阿里開源的 Java 診斷工具。線上排查問題,無需重啟;動態跟蹤 Java 程式碼;實時監控 JVM 狀態。Arthas 支援 JDK 6+,支援 Linux/Mac/Windows,採用命令列互動模式,同時提供豐富的 Tab 自動補全功能,進一步方便進行問題的定位和診斷。
Arthas可以通過簡單的命令互動模式,接入執行的JVM,快速定位和診斷線上程式執行的問題。在不重啟服務的情況下,實時,動態的修改相關程式碼,並實時生效,具體工作原理如下:
- 連線JVM,通過attach機制,通過attach pid連線正在執行的JVM
- 檢視及修改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-boot
是Arthas
的啟動程式,它啟動後,會列出所有的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-pattern
/method-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)
需要強調的點
-
ThreadLocal 資訊丟失
很多框架偷偷的將一些環境變數資訊塞到了發起呼叫執行緒的 ThreadLocal 中,由於呼叫執行緒發生了變化,這些 ThreadLocal 執行緒資訊無法通過 Arthas 儲存,所以這些資訊將會丟失。
-
引用的物件
需要強調的是,
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的官方文件。