Arthas原理與實戰指南
1. Arthas簡介
Arthas是阿里巴巴開源的Java診斷工具,其名字取自《魔獸世界》的人物阿爾薩斯。它面向線上問題定位,被廣泛應用于性能分析、定位問題、安全審計等場景。Arthas的核心價值在于它能夠在不修改應用代碼、不重啟Java進程的情況下,實時動態地監控和分析運行中的Java程序。
Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同時提供豐富的Tab自動補全功能。
2. 核心原理深度解析
2.1 Java Agent技術
Arthas基于Java Agent技術,Java Agent是JDK 1.5引入的一種能夠在不修改Java源代碼的情況下,動態修改Java字節碼的技術。
Java Agent通過以下兩種方式工作:
- 靜態加載:通過JVM啟動參數
-javaagent
指定 - 動態加載:通過Attach API動態附加到運行中的JVM
Arthas采用了動態加載的方式,使其能夠在Java應用運行過程中被加載。
2.2 Instrumentation API
Java的java.lang.instrument
包提供了一套API,允許Java Agent程序修改已加載的類的字節碼。Arthas利用這一API來實現類和方法的監控和分析。
關鍵接口和類:
Instrumentation
:提供注冊類文件轉換器、獲取所有已加載類等功能ClassFileTransformer
:類文件轉換器,用于修改類的字節碼Agent
:Agent程序的入口點,通過premain
或agentmain
方法啟動
2.3 JVM Attach機制
Attach機制允許一個JVM進程連接到另一個JVM進程,實現進程間通信。Arthas使用該機制動態加載Agent到目標JVM中。
核心實現在com.sun.tools.attach
包中,關鍵類有:
VirtualMachine
:代表一個JVM進程VirtualMachineDescriptor
:JVM進程的描述信息
2.4 ASM字節碼操作
Arthas使用ASM庫操作Java字節碼,通過修改字節碼來實現方法攔截、監控等功能。ASM是一個輕量級的字節碼操作框架,能夠動態生成和修改Java字節碼。
字節碼轉換過程:
- 讀取原始類字節碼
- 使用ASM分析字節碼結構
- 修改字節碼(如添加方法入口/出口的監控代碼)
- 返回修改后的字節碼
2.5 命令處理引擎
Arthas采用命令行交互方式,內部實現了一套完整的命令處理引擎:
- 命令解析:將用戶輸入解析為命令對象
- 命令執行:根據命令執行相應操作
- 結果渲染:將執行結果格式化輸出
3. 安裝與啟動詳解
3.1 安裝方式
方式一:使用arthas-boot(推薦)
# 下載啟動腳本
curl -O https://arthas.aliyun.com/arthas-boot.jar# 啟動
java -jar arthas-boot.jar
方式二:使用全量包
# 下載全量包
curl -O https://arthas.aliyun.com/arthas-packaging.jar# 解壓
java -jar arthas-packaging.jar# 啟動
cd arthas
./arthas.sh
方式三:使用as.sh
# 下載并安裝
curl -L https://arthas.aliyun.com/install.sh | sh# 啟動
./as.sh
3.2 啟動選項詳解
啟動Arthas時,可以指定多種參數:
# 指定目標Java進程
java -jar arthas-boot.jar [PID]# 指定目標進程名稱的關鍵字
java -jar arthas-boot.jar --select JAVA_HOME# 啟動時禁用某些命令
java -jar arthas-boot.jar --exclude-commands=jvm,thread# 指定端口號
java -jar arthas-boot.jar --telnet-port 9998 --http-port 9999# 以批處理模式執行命令
java -jar arthas-boot.jar --command "thread" -c "thread" > output.txt
3.3 連接方式
Arthas提供多種連接方式:
-
本地命令行模式:
直接在啟動終端操作 -
Telnet連接:
telnet 127.0.0.1 3658
-
WebSocket連接:
通過瀏覽器訪問http://127.0.0.1:8563/
-
HTTP API:
curl http://127.0.0.1:8563/api
4. 核心功能與命令詳解
4.1 JVM相關命令
dashboard - 系統實時數據面板
提供系統整體情況的實時數據,包括線程、內存、GC、運行環境等信息。
# 每5秒刷新一次
dashboard -i 5000# 只顯示前10個線程
dashboard -n 10
輸出示例:
ID NAME GROUP PRIORITY STATE %CPU DELTA_TIME TIME INTERRUPTED DAEMON
17 pool-2-thread-1 main 5 RUNNABLE 27 0.136 0:0.203 false false
21 pool-2-thread-5 main 5 RUNNABLE 26 0.132 0:0.096 false false
22 pool-2-thread-6 main 5 RUNNABLE 26 0.132 0:0.097 false false
......Memory used total max usage GC
heap 32M 155M 1820M 1.76% gc.ps_scavenge.count 118
ps_eden_space 14M 65M 672M 2.21% gc.ps_scavenge.time(ms) 1890
ps_survivor_space 4M 5M 5M 81.92% gc.ps_marksweep.count 5
ps_old_gen 12M 85M 1365M 0.91% gc.ps_marksweep.time(ms) 1140
jvm - JVM信息
# 顯示JVM信息
jvm# 同時顯示ClassLoader信息
jvm -c
輸出包含:
- Java運行時版本與廠商
- JVM參數
- 類加載統計
- JVM內存區域使用情況
- 垃圾收集器信息
- 操作系統和硬件信息
thread - 線程分析
# 顯示所有線程
thread# 查看指定線程的棧信息
thread 1# 查看最忙的前3個線程棧
thread -n 3# 查看阻塞其他線程的線程
thread -b# 查找指定狀態的線程
thread --state BLOCKED# 線程池信息
thread -i
線程池參數解析:
- corePoolSize: 核心線程數
- maximumPoolSize: 最大線程數
- keepAliveTime: 線程存活時間
- queueCapacity: 隊列容量
- taskCount: 已執行和未執行的任務總數
- completedTaskCount: 已完成的任務數
- largestPoolSize: 歷史最大線程數
- poolSize: 當前線程數
- activeCount: 當前活動線程數
sysprop - 系統屬性
# 查看所有系統屬性
sysprop# 查看指定屬性
sysprop java.version# 設置系統屬性
sysprop user.country US
heapdump - 堆轉儲
# 生成堆轉儲文件到指定路徑
heapdump /tmp/dump.hprof# 只轉儲活著的對象
heapdump --live /tmp/dump.hprof
4.2 類相關命令
sc - 查找類
# 模糊查找類
sc *List*# 查找指定類的詳細信息
sc -d java.util.ArrayList# 查找類的方法信息
sc -d -f java.util.ArrayList# 顯示類加載器信息
sc -c -d java.util.ArrayList# 指定類加載器查找
sc -c classLoaderHash *MathGame*
sm - 查找方法
# 查找類的所有方法
sm java.util.ArrayList# 查找方法的詳細信息
sm -d java.util.ArrayList add# 正則匹配方法
sm java.util.ArrayList "add|remove"
jad - 反編譯
# 反編譯指定類
jad com.example.demo.arthas.user.UserController# 指定反編譯結果輸出路徑
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java# 只反編譯指定的方法
jad com.example.demo.arthas.user.UserController getUserById
mc - 內存編譯
# 編譯指定Java文件
mc /tmp/UserController.java# 指定輸出目錄
mc -d /tmp/output /tmp/UserController.java# 指定ClassLoader編譯
mc -c 5a54a66 /tmp/UserController.java
redefine - 熱加載
# 重新加載類
redefine /tmp/output/com/example/demo/arthas/user/UserController.class# 指定ClassLoader
redefine -c 5a54a66 /tmp/output/com/example/demo/arthas/user/UserController.class# 批量重新加載
redefine -p /tmp/output/
4.3 方法相關命令
monitor - 方法監控
# 監控方法執行情況
monitor -c 5 com.example.demo.arthas.user.UserController * # 匹配正則表達式方法
monitor -c 5 com.example.demo.arthas.user.UserController get*# 監控異常統計
monitor -e -c 5 com.example.demo.arthas.user.UserController *# 監控匹配的構造函數
monitor -c 5 com.example.demo.arthas.user.UserController <init>
監控指標說明:
- timestamp: 時間戳
- class: 類名
- method: 方法名
- total: 調用次數
- success: 成功次數
- fail: 失敗次數
- rt: 平均響應時間(ms)
- fail-rate: 失敗率
watch - 方法觀察
# 觀察方法的入參和返回值
watch com.example.demo.arthas.user.UserController getUserById '{params, returnObj}' -x 3# 觀察異常信息
watch com.example.demo.arthas.user.UserController getUserById '{params, throwExp}' -e -x 2# 觀察入參和返回值,并按照條件過濾
watch com.example.demo.arthas.user.UserController getUserById '{params, returnObj}' 'params[0] > 100' -x 3# 觀察入參和返回值,限制次數
watch com.example.demo.arthas.user.UserController getUserById '{params, returnObj}' '#cost > 10' -n 3# 按表達式過濾,只有耗時大于10ms的才會輸出
watch com.example.demo.arthas.user.UserController getUserById '{params, returnObj, #cost}' '#cost > 10' -n 3 -x 3
watch支持的表達式工具類:
- params:參數列表
- returnObj:返回值
- throwExp:拋出的異常
- target:當前對象實例
- clazz:當前類
- method:當前方法
- #cost:執行耗時
trace - 方法調用鏈分析
# 跟蹤方法執行的調用鏈
trace com.example.demo.arthas.user.UserController getUserById# 指定最大展開層級
trace -j 2 com.example.demo.arthas.user.UserController getUserById# 按調用耗時過濾
trace com.example.demo.arthas.user.UserController getUserById '#cost > 10'# 只跟蹤本地方法
trace --skipJDKMethod false com.example.demo.arthas.user.UserController getUserById
輸出示例:
`---ts=2018-12-04 18:11:45;thread_name=http-nio-8080-exec-5;id=31;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@6bc168e5`---[10.127743ms] com.example.demo.arthas.user.UserController:getUserById()+---[0.060919ms] com.example.demo.arthas.user.UserController:getUserById:before()`---[9.732368ms] com.example.demo.arthas.user.UserRepository:findById()`---[9.499895ms] org.hibernate.jpa.internal.EntityManagerImpl:find()`---[9.187044ms] org.hibernate.jpa.internal.EntityManagerImpl:find()
stack - 調用棧跟蹤
# 查看調用來源
stack com.example.demo.arthas.user.UserController getUserById# 條件表達式過濾
stack com.example.demo.arthas.user.UserRepository findById 'params[0]==1'# 指定采樣次數
stack -n 5 com.example.demo.arthas.user.UserController getUserById
tt - 方法執行時空隧道
tt命令記錄方法執行的詳細信息,支持回放。
# 記錄方法執行過程
tt -t com.example.demo.arthas.user.UserController getUserById# 查看記錄的調用信息
tt -l# 查看記錄的詳細信息
tt -i 1000# 重新執行一次調用
tt -i 1000 -p# 指定方法入參重新執行
tt -i 1000 -p '{params[0] = 2}'# 條件過濾
tt -t com.example.demo.arthas.user.UserController getUserById 'params[0]==1'
4.4 增強功能
profiler - 性能剖析
# 查看profiler支持的事件
profiler list# 開始采樣,按CPU采樣
profiler start# 指定采樣事件
profiler start --event alloc# 指定文件輸出格式(支持svg、html、jfr等)
profiler start --format html# 采樣一段時間后停止
profiler stop# 將結果保存到指定文件
profiler stop --file /tmp/result.html# 支持火焰圖
profiler start --event cpu --format svg
profiler stop --file /tmp/cpu.svg
vmtool - JVM工具
# 獲取對象
vmtool --action getInstances --className java.lang.String --limit 10# 查看對象信息
vmtool --action getInstances --className com.example.demo.arthas.user.User --express 'instances[0].username'# 強制GC
vmtool --action forceGc
ognl - 執行OGNL表達式
# 獲取靜態字段
ognl '@com.example.demo.arthas.user.UserService@INSTANCE'# 調用靜態方法
ognl '@java.lang.System@currentTimeMillis()'# 獲取變量值
ognl '#user=@com.example.demo.arthas.user.UserController@userService.findById(1), #user.username'# 調用對象方法
ognl '#user=@com.example.demo.arthas.user.UserController@userService.findById(1), #user.setUsername("arthas"), #user'
5. 實戰案例詳解
5.1 CPU使用率過高問題分析
當應用CPU使用率異常升高時,使用Arthas可以快速定位問題:
實戰步驟:
-
首先執行
dashboard
查看系統整體情況:dashboard -n 10
-
發現有線程CPU使用率很高,執行
thread
命令查看線程狀態:# 查看占用CPU最高的3個線程 thread -n 3
-
定位到問題線程,查看其棧信息:
thread 16234
-
發現可疑方法,使用
trace
跟蹤執行鏈路:trace com.example.service.OrderService calculatePrice '#cost > 200'
-
使用
profiler
進行火焰圖分析:profiler start --event cpu # 等待30秒 profiler stop --format svg --file /tmp/cpu.svg
5.2 內存泄漏分析
實戰步驟:
-
首先執行
dashboard
和memory
觀察內存使用情況:# 觀察內存趨勢 dashboard -i 5000# 查看詳細內存信息 memory
-
發現Old區內存持續增長,使用
heapdump
導出堆內存:heapdump --live /tmp/heap.hprof
-
使用MAT分析堆轉儲文件(離線分析)
-
根據MAT分析結果,定位到可疑類,使用
vmtool
查看實例:vmtool --action getInstances --className com.example.cache.UserCache --limit 10
-
使用
ognl
查看對象詳情:ognl '#cache=@com.example.cache.UserCache@INSTANCE, #cache.cacheMap.size()'
-
使用
watch
監控可疑方法:watch com.example.cache.UserCache put '{params, target.cacheMap.size()}' -x 3
5.3 線上修復Bug
實戰步驟:
-
首先定位到問題代碼,使用
jad
反編譯:jad --source-only com.example.service.OrderService > /tmp/OrderService.java
-
修改源代碼,修復Bug:
vim /tmp/OrderService.java
-
使用
mc
編譯修改后的代碼:mc -d /tmp/classes /tmp/OrderService.java
-
使用
redefine
熱加載修改后的類:redefine /tmp/classes/com/example/service/OrderService.class
-
使用
watch
驗證修復效果:watch com.example.service.OrderService calculatePrice '{params, returnObj}' -x 3
5.4 定位接口超時問題
實戰步驟:
-
首先用
trace
跟蹤超時接口:trace com.example.controller.ApiController handleRequest '#cost > 1000'
-
發現有方法特別耗時,使用
stack
查看其調用來源:stack com.example.service.RemoteService requestData
-
使用
watch
觀察方法的入參和返回值:watch com.example.service.RemoteService requestData '{params, returnObj, #cost}' -x 3
-
使用
tt
記錄多次調用,分析變化趨勢:tt -t com.example.service.RemoteService requestData
-
回放某次執行,調試分析:
tt -i 1000 -p
6. 高級應用場景
6.1 Spring Boot應用診斷
診斷Spring應用的常用命令組合:
# 查找所有Controller
sc -d *Controller# 查看一個Bean的詳細信息
ognl '#context=@org.springframework.web.context.support.WebApplicationContextUtils@getWebApplicationContext(#request.getServletContext()), #context.getBean("userService")'# 查找所有RequestMapping
ognl '#springContext=@org.springframework.web.context.ContextLoader@getCurrentWebApplicationContext(), #springContext.getBean("requestMappingHandlerMapping").getHandlerMethods().entrySet()' -x 2
6.2 動態日志調整
運行時調整日志級別是Arthas的強大功能:
# 查看logger信息
logger# 查看指定logger信息
logger -n org.springframework.web# 修改日志級別
logger --name org.springframework.web --level debug# 在方法調用時臨時調高日志級別
watch com.example.service.UserService update '{params, returnObj}' -x 3 '#cost>100' 'logger:org.springframework.web:TRACE'
6.3 性能優化
對應用進行性能優化的常用方法:
# 查找熱點類和方法
profiler start --event cpu
profiler stop --format html --file /tmp/cpu-profiler.html# 用watch命令觀察方法執行次數與耗時
monitor -c 5 com.example.service.* *# 對比優化前后性能變化
tt -t com.example.service.OrderService calculatePrice
# 優化后
tt -t com.example.service.OrderService calculatePrice
tt -l
6.4 多應用實例問題
當有多個同類型應用實例時,如何診斷問題:
# 啟動時選擇特定實例
java -jar arthas-boot.jar --select "demo-app"# 設置唯一tunnel id
java -jar arthas-boot.jar --tunnel-server "ws://tunnel-server:7777/ws" --agent-id "app1_instance1"# 使用Web Console連接特定實例
http://tunnel-server:8080/arthas-web-console/index.html?agentId=app1_instance1
7. 最佳實踐與注意事項
7.1 性能影響控制
Arthas雖然強大,但使用不當會影響線上系統性能:
-
避免長時間使用trace/watch等命令:
# 限制采樣次數 trace -n 10 com.example.service.OrderService calculatePrice# 限制命令執行時間 trace --duration 30 com.example.service.OrderService calculatePrice
-
使用條件表達式過濾:
# 只監控耗時超過100ms的調用 trace com.example.service.OrderService calculatePrice '#cost > 100'
-
合理設置采樣間隔:
# 增加采樣間隔,降低對系統的影響 monitor -c 10 -i 5000 com.example.service.OrderService calculatePrice
7.2 安全措施
生產環境使用Arthas需注意以下安全事項:
-
設置訪問認證:
java -jar arthas-boot.jar --username admin --password admin
-
使用tunnel server模式保證網絡安全:
java -jar arthas-boot.jar --tunnel-server 'ws://tunnel-server:7777/ws'
-
限制命令使用:
java -jar arthas-boot.jar --exclude-commands=jad,mc,redefine
-
及時退出Arthas會話:
# 使用完后退出 quit# 完全退出,卸載Agent stop
7.3 版本兼容性
Arthas的不同版本可能有命令差異,建議:
- 總是使用與JDK版本兼容的Arthas版本
- 定期更新Arthas到最新版本以獲取bug修復和新功能
- 在測試環境驗證Arthas命令后再在生產環境使用
7.4 與其他工具配合使用
Arthas可以與其他工具結合使用,形成完整的問題診斷體系:
- 與MAT配合分析內存問題
- 與JMC/JFR結合進行性能分析
- 與ELK結合進行日志分析
- 與APM工具結合進行全鏈路追蹤
8. 總結
Arthas作為一款強大的Java診斷工具,通過Java Agent技術實現了對JVM運行時的深度觀測和操控能力。它的優勢在于:
- 無侵入性:不需要修改應用代碼或重啟應用
- 實時分析:能夠動態獲取運行時數據
- 功能豐富:從線程分析到字節碼操作,覆蓋了診斷需求
- 易于使用:命令行界面簡單直觀
掌握Arthas使你在面對復雜的Java生產環境問題時,能夠像手術刀一樣精準定位并解決問題,真正做到知其所以然。
在實際應用中,建議通過大量實踐熟悉各個命令的使用場景和優缺點,形成自己的問題診斷方法論。通過不斷實踐,你會發現Arthas不僅是一個工具,更是一種解決問題的思路和方法。