Arthas

Arthas 是阿里巴巴开源的 Java 诊断工具,无需重启应用、无需修改代码,可在生产环境动态查看类加载情况、方法参数/返回值/异常、线程栈、内存使用等,是排查线上问题的核心工具。

JProfiler 的区别:JProfiler 适合本地性能剖析,Arthas 适合线上动态诊断。


安装与启动

# 方式一:下载 jar 包(推荐)
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar          # 自动列出 JVM 进程,选择编号后 attach
 
# 方式二:一键安装脚本
curl -L https://arthas.aliyun.com/install.sh | sh
./as.sh
 
# 方式三:Docker 环境(attach 到容器内的 JVM)
java -jar arthas-boot.jar --target-ip 0.0.0.0

启动后进入交互式 Console,输入 help 查看所有命令。


基础命令

# 查看 JVM 进程信息(已 attach 的进程)
version
 
# 查看系统属性
sysprop                             # 列出所有
sysprop java.version               # 查看单个
 
# 查看环境变量
sysenv
sysenv JAVA_HOME
 
# JVM 整体运行状态(内存、GC、线程数)
jvm
 
# 实时仪表盘(CPU、内存、GC、线程,每隔 5s 刷新)
dashboard
 
# 退出 Arthas(不影响目标进程)
quit
# 完全停止 Arthas(detach 并退出)
stop

类与类加载器

# 查看类的加载信息(类加载器、代码源路径)
sc com.example.service.UserService
sc -d com.example.service.UserService   # 详细信息(包含字段、方法)
 
# 通配符搜索
sc com.example..*Service
 
# 反编译类(查看字节码对应的源码,确认部署版本正确)
jad com.example.service.UserService
 
# 反编译指定方法
jad com.example.service.UserService getUserById
 
# 查看类的方法列表
sm com.example.service.UserService
sm -d com.example.service.UserService getUserById   # 方法详情
 
# 从 JVM 中导出类的 class 文件
dump com.example.service.UserService

watch — 观察方法调用

watch 是最常用的命令,拦截方法的入参、返回值、异常,不修改代码、不重启

# 基本语法
watch 类名 方法名 观察表达式 [条件] [-x 展开深度]
 
# 观察返回值(-x 2 展开两层对象)
watch com.example.service.UserService getUserById returnObj -x 2
 
# 同时观察入参和返回值
watch com.example.service.UserService getUserById '{params, returnObj}' -x 3
 
# 观察入参
watch com.example.service.OrderService createOrder '{params[0]}' -x 2
 
# 只观察抛出异常的调用
watch com.example.service.UserService getUserById throwExp -e
 
# 条件过滤:只观察第一个参数等于 1001 的调用
watch com.example.service.UserService getUserById returnObj 'params[0]==1001L'
 
# 观察耗时超过 200ms 的调用
watch com.example.service.OrderService processOrder '{params, returnObj, throwExp}' '#cost>200' -x 2
 
# 在方法入口观察(默认在方法出口)
watch com.example.service.UserService getUserById params -b

trace — 方法调用链路追踪

追踪方法内部调用树及每一步耗时,快速定位哪个子方法慢:

# 追踪方法调用链
trace com.example.service.OrderService createOrder
 
# 设置最大追踪深度(防止链路过深)
trace com.example.service.OrderService createOrder --skipJDKMethod false -n 5
 
# 只追踪耗时超过 100ms 的调用
trace com.example.service.OrderService createOrder '#cost>100'

输出示例:

`---ts=2024-01-15 10:23:11; thread_name=http-nio-8080-exec-1; id=xxx; is_daemon=true; ---[198ms] com.example.service.OrderService:createOrder()
    +---[0ms] com.example.dao.UserDao:findById()
    +---[195ms] com.example.dao.InventoryDao:checkStock()   ← 慢!
    `---[2ms] com.example.dao.OrderDao:save()

stack — 输出方法调用来源

查看某方法是被哪些调用路径触发的:

stack com.example.dao.UserDao findById
 
# 只在特定条件下触发
stack com.example.service.UserService getUserById 'params[0]==1001L'

monitor — 方法调用统计

统计方法在一段时间内的调用次数、成功率、平均耗时:

# 每 10 秒统计一次
monitor com.example.service.UserService getUserById -c 10

输出:

timestamp            class                method   total  success  fail  avg-rt(ms)  fail-rate
2024-01-15 10:23     UserService         getUserById  42    41       1     12.4        2.38%

tt — 时光机(记录并重放调用)

tt(TimeTunnel)记录每次方法调用的快照,事后可查询和重放:

# 开始记录(最多 100 条)
tt -t com.example.service.UserService getUserById -n 100
 
# 查看记录列表
tt -l
 
# 查看某条记录详情(INDEX 是记录编号)
tt -i 1003 -x 3
 
# 重放某条调用(用同样的入参再次执行)
tt -i 1003 -p

ognl — 动态执行表达式

在目标 JVM 中执行任意 Java 代码,无需修改源码:

# 调用静态方法
ognl "@com.example.util.ConfigUtil@getProperty('app.name')"
 
# 访问 Spring Bean(通过 ApplicationContext)
ognl "#ctx=@com.example.SpringContextHolder@getContext(), #ctx.getBean('userService').countAll()"
 
# 修改静态字段(临时调整配置,慎用)
ognl "@com.example.config.AppConfig@MAX_RETRY=5"
 
# 执行复杂表达式
ognl "new java.util.Date()"
ognl "@java.lang.System@currentTimeMillis()"

线程诊断

# 查看所有线程(含 CPU 占用)
thread
 
# 查看 CPU 占用最高的 N 个线程
thread -n 5
 
# 打印指定线程的栈
thread 42
 
# 查找死锁
thread -b
 
# 过滤阻塞状态的线程
thread --state BLOCKED

内存与 GC

# 堆内存使用概览(分代)
memory
 
# 触发一次 Full GC(谨慎,生产环境会暂停应用)
ognl "@java.lang.System@gc()"
 
# 查看 GC 统计
jvm | grep -A 10 "GARBAGE-COLLECTORS"

热更新类(谨慎使用)

在不重启应用的情况下替换类的字节码(仅用于紧急修复,不可修改方法签名/字段):

# 1. 反编译查看现有代码
jad --source-only com.example.service.UserService > /tmp/UserService.java
 
# 2. 修改 /tmp/UserService.java
 
# 3. 编译(使用与目标 JVM 相同版本的 javac)
mc -c <classloader-hash> /tmp/UserService.java -d /tmp/
 
# 4. 重新加载
redefine /tmp/com/example/service/UserService.class

热更新仅修改方法体,不能增减字段/方法,重启后失效。生产环境应走正式发布流程。


常用排查场景

问题命令
接口返回值异常watch 观察方法出参
接口慢,不知道慢在哪trace 追踪调用链耗时
CPU 飙高thread -n 5 找热点线程 + jad 看代码
内存泄漏memory + ognl 查堆对象
方法被意外调用stack 反查调用来源
确认部署版本正确jad 反编译对比代码
临时修改配置生效ognl 修改静态字段
死锁排查thread -b

相关链接

  • JProfiler — 本地性能剖析(CPU / 内存 / 线程可视化)
  • 日志 — 结合日志定位问题上下文
  • 链路追踪 — 分布式全链路定位
  • 性能调优 — JVM 调优与 GC 策略