Arthas 簡介
Arthas 是 Alibaba 開源的 Java 診斷工具,根據官方介紹,它提供了如下工功能:
官方文檔地址: https://alibaba.github.io/arthas/
github 源碼地址: https://github.com/alibaba/arthas
Arthas 安裝
啟動 Arthas
# 下載 arthas
curl -O https://alibaba.github.io/arthas/arthas-boot.jar
# 通過如下命令啟動
java -jar arthas-boot.jar
選擇進程 id 按下回車,就可以連接到對應的 java 應用,首次啟動會下載一些文件到 "C:/Users/${user}/.arthas/lib/3.2.0/arthas
" 目錄
arthas 啟動支持多個參數,可以使用 -h
查看
EXAMPLES:
java -jar arthas-boot.jar <pid>
java -jar arthas-boot.jar --target-ip 0.0.0.0
java -jar arthas-boot.jar --telnet-port 9999 --http-port -1
java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws'
java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws'
--agent-id bvDOe8XbTM2pQWjF4cfw
java -jar arthas-boot.jar --stat-url 'http://192.168.10.11:8080/api/stat'
java -jar arthas-boot.jar -c 'sysprop; thread' <pid>
java -jar arthas-boot.jar -f batch.as <pid>
java -jar arthas-boot.jar --use-version 3.2.0
java -jar arthas-boot.jar --versions
java -jar arthas-boot.jar --session-timeout 3600
java -jar arthas-boot.jar --attach-only
java -jar arthas-boot.jar --repo-mirror aliyun --use-http
WIKI:
https://alibaba.github.io/arthas
help
arthas 連接成功后,使用 help 可以看到提供的一些命令
每個命令可以使用 -h
參數查看幫助信息,里面有EXAMPLES
和WIKI
鏈接
webconsole
arthas 啟動后,可以通過瀏覽器進行訪問,地址 http://localhost:8563/
退出
如果只是退出當前的連接,可以用quit
或者exit
命令。Attach到目標進程上的arthas還會繼續運行,端口會保持開放,下次連接時可以直接連接上。
如果想完全退出arthas,可以執行stop
命令。
Arthas 命令
dashboard
查看面板信息,主要包含cpu 內存使用信息,可以按 Ctrl+C
或者 輸入 q
退出
數據說明
- ID: Java級別的線程ID,注意這個ID不能跟jstack中的nativeID一一對應
- NAME: 線程名
- GROUP: 線程組名
- PRIORITY: 線程優先級, 1~10之間的數字,越大表示優先級越高
- STATE: 線程的狀態
- CPU%: 線程消耗的cpu占比,采樣100ms,將所有線程在這100ms內的cpu使用量求和,再算出每個線程的cpu使用占比。
- TIME: 線程運行總時間,數據格式為
分:秒
- INTERRUPTED: 線程當前的中斷位狀態
- DAEMON: 是否是daemon線程
thread
查看線程使用情況
# 查看所有線程信息
thread
# 查看具體線程的棧,查看線程ID 16的棧:
thread 16
# 查看CPU使用率top n線程的棧
thread -n 3
# 查看5秒內的CPU使用率top n線程棧
thread -n 3 -i 5000
# 查找線程是否有阻塞
thread -b
sysprop
查看當前JVM的系統屬性,支持 pipeline
sysprop # 查詢所有屬性
sysprop key # 查看key對應的屬性
sysprop key value # 修改屬性值
sysprop | grep java # 查詢包含java的屬性
sysprop | wc -l # 統計數量
sysenv
查看當前JVM的環境屬性
用法和 sysprop 類似,不支持修改
logger
查看logger信息,更新logger level
logger # 查看所有logger對象信息
logger -n [name] # 查看名為name的logger信息
logger -c [classloader] -n [name] -l ERROR # 修改名為name的logger級別為ERROR,需指定類加載器
sc
查看JVM已加載的類信息
sc -d org.apache.commons.lang.StringUtils # 查看StringUtils詳細信息
sc -d org/apache/commons/lang/StringUtils # 查看StringUtils詳細信息
sc -d *StringUtils # 查看StringUtils,根據*匹配
sc -d -f org.apache.commons.lang.StringUtils # 查看類及成員變量信息,f要配合d使用才有效
sm
查看已加載類的方法信息,用法和 sc
類似
sm java.lang.String # 查看String的所有方法
sm -d org.apache.commons.lang.StringUtils # 查看String方法詳情
sm -d org/apache/commons/lang/StringUtils # 查看String方法詳情
sm *StringUtils * # 查看String方法,根據*匹配
dump
dump 已加載類的 bytecode 到特定目錄
dump java.lang.String # dump java.lang.String.class文件
dump java.lang.* # dump 批量dump
dump -d /tmp/output java.lang.String # dump到指定目錄
dump org/apache/commons/lang/StringUtils # dump,支持目錄格式
dump *StringUtils # dump,根據*匹配
jad
反編譯指定已加載類的源碼
jad java.lang.String # 反編譯String類
jad java.lang.String toString # 反編譯指定方法
jad --source-only java.lang.String # 反編繹時只顯示源代碼
jad -c 39eb305e org/apache/log4j/Logger # 反編譯指定classloader
classloader
查看classloader的繼承樹,urls,類加載信息
classloader # 列出所有classLoader
classloader -t # 樹形結構列出所有classLoader
classloader -l # 統計每個classLoader加載類數量
classloader -c 327a647b # 查看具體的classLoader
classloader -a # 列出所有加載的類
classloader -c 659e0bfd --load demo.MathGame # 使用指定classLoader加載類
mc
編譯.java
文件生成.class
mc /tmp/Test.java # 編譯Test.java
mc -c 327a647b /tmp/Test.java # 使用 -c 指定classLoader
mc -d /tmp/output /tmp/ClassA.java /tmp/ClassB.java # 使用 -d 指定輸出目錄
redefine
加載外部的.class
文件
redefine
命令和jad
/watch
/trace
/monitor
/tt
等命令會沖突。執行完redefine
之后,如果再執行上面提到的命令,則會把redefine
的字節碼重置。 原因是jdk本身redefine和Retransform是不同的機制,同時使用兩種機制來更新字節碼,只有最后修改的會生效。
redefine /tmp/Test.class # 加載類
redefine -c 327a647b /tmp/Test.class /tmp/Test\$Inner.class # 指定classLoader
通常結合 jad/mc 使用
- jad命令反編譯,然后可以用其它編譯器,比如vim來修改源碼
- mc命令來內存編譯修改過的代碼
- 用redefine命令加載新的字節碼
redefine的限制
- 不允許新增加 field/method
- 正在跑的函數,沒有退出不能生效
watch
方法執行數據觀測
# 方法調用前觀察,可以是非靜態方法
watch -b org.apache.commons.lang.StringUtils isBlank params
# 在方法結束之后(正常返回和異常返回)觀察
watch -f org.apache.commons.lang.StringUtils isBlank returnObj
# 指定輸出結果的屬性遍歷深度,2
watch org.apache.commons.lang.StringUtils isBlank '{params, target, returnObj}' -x 2
# 耗時100ms時輸出
watch *StringUtils isBlank params '#cost>100'
@RestController
public class UserController {
@GetMapping("/user/{id}")
public User getUser(@PathVariable Integer id) {
if (null == id) {
throw new IllegalArgumentException("id can not be null");
}
if (id < 1) {
throw new IllegalArgumentException("id must be greater than 1");
}
return new User(id, "zhangsan");
}
}
使用如下 watch 命令,然后訪問 http://localhost:9090/user/10
watch com.soulballad.usage.arthasdemo.web.UserController getUser "{params,target,returnObj}" -x 2 -b -s -n 2
- 參數里
-n 2
,表示只執行兩次 - 輸出結果中,第一次輸出的是方法調用前的觀察結果,第二次輸出的是方法返回后的表達式的結果
- 結果的輸出順序和事件發生的先后順序一致,和命令中
-s -b
的順序無關
trace
方法內部調用路徑,並輸出方法路徑上的每個節點上耗時
trace org.apache.commons.lang.StringUtils isBlank # 查看isBlank方法調用路徑及耗時
trace *StringUtils isBlank # 使用*匹配
trace *StringUtils isBlank '#cost>100' # 過濾只輸出耗時大於100ms的記錄
# 正則表達式,支持多個路徑記錄
trace -E com.test.ClassA|org.test.ClassB method1|method2|method3
trace demo.MathGame run -n 5 # 只執行5次
trace demo.MathGame run --skipJDKMethod false # 不跳過jdk中方法,默認為true
stack
輸出當前方法被調用的調用路徑
支持條件過濾和 ognl 表達式
tt
方法執行數據的時空隧道,記錄下指定方法每次調用的入參和返回信息,並能對這些不同的時間下調用進行觀測
tt -t *StringUtils isEmpty # 記錄isEmpty方法調用
tt -t *StringUtils isEmpty params[0].length==1 # 解決方法重載
tt -l # 查看所有記錄
tt -i 1000 # 查詢index為1000的記錄詳情
tt -i 1000 -p # 根據index重新觸發調用
tt -i 1000 -p --replay-times 3 --replay-interval 3000 # 指定觸發間隔和次數
tt --delete-all # 刪除所有記錄
ognl
執行ognl表達式
ognl '@java.lang.System@out.println("hello")' # 調用靜態函數
ognl -x 2 '@Singleton@getInstance()' # 2層
ognl '@Demo@staticFiled' # 輸出靜態變量值
# 把java.home和java.runtime.name的系統屬性放到一個集合中輸出
ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
ognl -c 5d113a51 '@com.taobao.arthas.core.GlobalOptions@isDump' # 輸出false