Arthas是阿里巴巴开源的Java诊断工具,其名字取自《魔兽世界》的人物阿尔萨斯。它面向线上问题定位,被广泛应用于性能分析、定位问题、安全审计等场景。Arthas的核心价值在于它能够在不修改应用代码、不重启Java进程的情况下,实时动态地监控和分析运行中的Java程序。
Arthas支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的Tab自动补全功能。
Arthas基于Java Agent技术,Java Agent是JDK 1.5引入的一种能够在不修改Java源代码的情况下,动态修改Java字节码的技术。
Java Agent通过以下两种方式工作:
-javaagent
指定Arthas采用了动态加载的方式,使其能够在Java应用运行过程中被加载。
Java的java.lang.instrument
包提供了一套API,允许Java Agent程序修改已加载的类的字节码。Arthas利用这一API来实现类和方法的监控和分析。
关键接口和类:
Instrumentation
:提供注册类文件转换器、获取所有已加载类等功能ClassFileTransformer
:类文件转换器,用于修改类的字节码Agent
:Agent程序的入口点,通过premain
或agentmain
方法启动Attach机制允许一个JVM进程连接到另一个JVM进程,实现进程间通信。Arthas使用该机制动态加载Agent到目标JVM中。
核心实现在com.sun.tools.attach
包中,关键类有:
VirtualMachine
:代表一个JVM进程VirtualMachineDescriptor
:JVM进程的描述信息Arthas使用ASM库操作Java字节码,通过修改字节码来实现方法拦截、监控等功能。ASM是一个轻量级的字节码操作框架,能够动态生成和修改Java字节码。
字节码转换过程:
Arthas采用命令行交互方式,内部实现了一套完整的命令处理引擎:
方式一:使用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
启动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
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
提供系统整体情况的实时数据,包括线程、内存、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
# 同时显示ClassLoader信息
jvm -c
输出包含:
# 显示所有线程
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 java.version
# 设置系统属性
sysprop user.country US
# 生成堆转储文件到指定路径
heapdump /tmp/dump.hprof
# 只转储活着的对象
heapdump --live /tmp/dump.hprof
# 模糊查找类
sc *List*
# 查找指定类的详细信息
sc -d java.util.ArrayList
# 查找类的方法信息
sc -d -f java.util.ArrayList
# 显示类加载器信息
sc -c -d java.util.ArrayList
# 指定类加载器查找
sc -c classLoaderHash *MathGame*
# 查找类的所有方法
sm java.util.ArrayList
# 查找方法的详细信息
sm -d java.util.ArrayList add
# 正则匹配方法
sm java.util.ArrayList "add|remove"
# 反编译指定类
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
# 编译指定Java文件
mc /tmp/UserController.java
# 指定输出目录
mc -d /tmp/output /tmp/UserController.java
# 指定ClassLoader编译
mc -c 5a54a66 /tmp/UserController.java
# 重新加载类
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/
# 监控方法执行情况
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 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支持的表达式工具类:
# 跟踪方法执行的调用链
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 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 -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'
# 查看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 --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 '@com.example.demo.arthas.user.UserService@INSTANCE'
# 调用静态方法
ognl '@java.lang.System@currentTimeMillis()'
# 获取变量值
ognl '#[email protected]@userService.findById(1), #user.username'
# 调用对象方法
ognl '#[email protected]@userService.findById(1), #user.setUsername("arthas"), #user'
当应用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
实战步骤:
首先执行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 '#[email protected]@INSTANCE, #cache.cacheMap.size()'
使用watch
监控可疑方法:
watch com.example.cache.UserCache put '{params, target.cacheMap.size()}' -x 3
实战步骤:
首先定位到问题代码,使用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
实战步骤:
首先用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
诊断Spring应用的常用命令组合:
# 查找所有Controller
sc -d *Controller
# 查看一个Bean的详细信息
ognl '#context=@org.springframework.web.context.support.WebApplicationContextUtils@getWebApplicationContext(#request.getServletContext()), #context.getBean("userService")'
# 查找所有RequestMapping
ognl '#[email protected]@getCurrentWebApplicationContext(), #springContext.getBean("requestMappingHandlerMapping").getHandlerMethods().entrySet()' -x 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'
对应用进行性能优化的常用方法:
# 查找热点类和方法
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
当有多个同类型应用实例时,如何诊断问题:
# 启动时选择特定实例
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
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
生产环境使用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
Arthas的不同版本可能有命令差异,建议:
Arthas可以与其他工具结合使用,形成完整的问题诊断体系:
Arthas作为一款强大的Java诊断工具,通过Java Agent技术实现了对JVM运行时的深度观测和操控能力。它的优势在于:
掌握Arthas使你在面对复杂的Java生产环境问题时,能够像手术刀一样精准定位并解决问题,真正做到知其所以然。
在实际应用中,建议通过大量实践熟悉各个命令的使用场景和优缺点,形成自己的问题诊断方法论。通过不断实践,你会发现Arthas不仅是一个工具,更是一种解决问题的思路和方法。