主题
Arthas 实战:不停机定位线上问题的十个命令
线上事故的决策困境
服务 RT 突然飙了,CPU 打满,但你不能重启——重启会丢失现场,GC 日志可能被 -Xloggc 循环覆盖,jmap dump 十几秒的 STW 在高峰期就是事故再加倍。你要在进程活着的时候看清楚它里面在干什么,但不能动它。
JFR 可以事后分析,但需要 JDK 自带支持和配置;jstack 只能看线程栈的快照;jmap 不能看方法入参。Arthas 的定位是"在线交互式诊断"——attach 到目标 JVM 后,你可以在终端里实时观测方法调用的入参、返回值、异常、耗时,反编译线上 class 确认版本,甚至热修复一行代码。
下面按排查场景组织十个高频命令,附 2 个真实案例。
十命令排查手册
1. dashboard:一眼看全貌
dashboard 是 Arthas 的欢迎页,输出当前线程的 CPU 和内存概览,跟 top 的进程级信息互补:
bash
# 连上后先跑这个
dashboard输出分三块:线程排行(按 CPU 使用率倒序)、内存分区(heap/non-heap 各代用量)、GC 统计(次数、耗时)。如果某个线程 CPU 持续 30%+ 且状态是 RUNNABLE,它就是后续排查的目标。
比 top -Hp 好的地方:dashboard 不需要前面 top 找进程、记 pid、转十六进制那一套,Arthas attach 后直接看。
2. thread:从线程栈到锁信息
thread 既可以看线程列表,也可以看具体线程的栈,还能顺便检测死锁:
bash
# 找 CPU 最忙的线程(替代 top -Hp)
thread -n 3
# 看线程 43 的完整栈
thread 43
# 检测死锁
thread -b-b 参数在排查"服务卡死"时极有用:它会扫描所有线程等锁的依赖关系,能发现 java.lang.management.ThreadMXBean.findDeadlockedThreads() 后面是多强的锁链。和 jstack 的区别是,jstack 输出的死锁检测是 JVM 自动做的,但通常藏在几百行线程栈里,而 thread -b 直接筛选出死锁的线程。
3. jad:反编译确认线上版本
"这个 bug 真的修了吗?"——线上 class 和代码仓库的版本经常对不上。jad 反编译目标类,直接看字节码反编译后的 Java 源码:
bash
jad com.example.order.OrderService
# 只看方法体
jad com.example.order.OrderService createOrder
# 跳过行号,只看逻辑
jad --source-only com.example.order.OrderService生产上的常见用法:运维说"上个月发的修复包已经打了",但 jad 一看,方法体里根本没有那行修复逻辑。不用怀疑,就是包没打对。
4. watch:方法入参、返回值、异常,一个不落
watch 是观察方法调用的杀手锏。相比 jstack 只能看线程栈(知道在哪个方法,但不知道参数和返回值),watch 可以把方法调用的上下文全部抓出来:
bash
# 观察 createOrder 方法,打印入参和返回值,只抓前 2 次
watch com.example.order.OrderService createOrder "{params, returnObj}" -x 2 -n 2
# 条件观察:只抓金额大于 10000 的调用
watch com.example.order.OrderService createOrder "{params, returnObj}" "params[0].amount > 10000" -x 2
# 抓异常:只抓抛异常的调用
watch com.example.order.OrderService createOrder "{params, throwExp}" -e -x 2-x 2 控制展开深度,复杂对象展开太深可能刷屏,一般 2 就够了。-n 2 限制抓取次数,避免被高频调用刷满终端。
watch 的常见场景:接口返回了不该返回的数据,但日志只打了"success=true",watch 抓返回值,直接看到对象里多了什么不该有的字段。
5. trace:方法调用链耗时
接口慢,但不知道慢在哪个环节——trace 把方法内每一层调用的耗时都拆出来:
bash
trace com.example.order.OrderService createOrder -n 3输出类似:
`---ts=2026-09-14 10:00:00;thread_name=http-nio-8080-exec-1;id=23;is_daemon=true;priority=5;
`---[10.1234ms] com.example.order.OrderService:createOrder()
`---[9.8765ms] com.example.order.OrderService:validateOrder()
`---[9.5432ms] com.example.order.OrderService:checkInventory()
`---[9.2100ms] com.example.order.InventoryClient:checkStock()
`---[9.1000ms] java.net.HttpURLConnection:connect()这一看就清楚了:9.2ms 里 9.1ms 花在网络调用上,业务逻辑几乎不耗时。根因是远程库存服务 RT 高。
trace 和 watch 的区别:watch 关注"具体是什么值",trace 关注"时间花在了哪里"。两个命令经常配合使用——先 trace 找到慢的方法,再 watch 抓那个方法的具体参数。
6. monitor:方法调用统计(QPS、耗时分布)
想看某接口在压测下的性能表现,或者观察生产上某个方法的调用频率,用 monitor:
bash
monitor -c 5 com.example.order.OrderService createOrder每 5 秒输出一次统计:
method count success fail avg-rt(ms)
com.example.order.OrderService:createOrder 123 121 2 15.3count 是调用次数,success/fail 是成功/失败次数,avg-rt 是平均响应时间。monitor 告诉你的不是"慢不慢",而是"慢的频率和趋势"——有时候接口没变慢但失败率在涨,可能是下游依赖出了问题。
7. tt:时空穿梭,记录方法调用历史
tt(TimeTunnel)是 watch 的升级版——把方法调用的上下文存下来,事后可以逐一回放:
bash
# 记录 OrderService 的 createOrder 方法调用
tt -t com.example.order.OrderService createOrder -n 5
# 查看记录列表
tt -l
# 回放第 1000 次调用(重新执行一次)
tt -p 1000tt 的典型场景:一个偶发 bug 只看日志抓不到(日志打的是"处理失败",但入参没打),用 tt -t 记录 5 次调用,等 bug 复现后,去回放那次调用,入参、返回值、异常全部重现。
注意:tt -p 回放会真的重新执行方法,如果方法有副作用(写数据库、发消息),回放会再执行一次。所以只适合读操作或幂等的场景。
8. ognl:表达式引擎,随时执行任意代码
ognl 是 Arthas 隐藏的大招——它可以在线执行 Java 表达式,读取/修改任意对象的字段,调用静态方法,甚至手动触发 GC:
bash
# 查看 Spring 容器中某个 bean 的字段值
ognl "@com.example.config.AppConfig@getEnv()"
# 修改某个静态字段(紧急修复开关)
ognl '#bean=@com.example.config.FeatureFlag@INSTANCE, #bean.setFlag("new_feature", false)'
# 查看 Spring 上下文中某个 bean 的配置
ognl '#ctx=@com.example.SpringContext@getApplicationContext(), #ctx.getBean("orderService").getTimeout()'
# 手动触发 GC
ognl '@java.lang.System@gc()'ognl 的用法几乎等于"在你的 JVM 里开了一个 REPL"。生产上慎用——ognl 能改任何字段,一个错误表达式可能直接改坏线上状态。
9. profiler:生成火焰图(async-profiler)
Arthas 内置了 async-profiler 的火焰图生成能力:
bash
# 启动 CPU 采样 30 秒
profiler start
# 30 秒后停止并生成火焰图
profiler stop --format html -o /tmp/flamegraph.html生成的火焰图 HTML 文件可以直接 scp 到本地浏览器打开,横向是时间,纵向是调用栈,宽条表示该方法在采样期间占用的 CPU 时间最多。比 jstack 多次打栈后人工拼接更直观。
profiler start 和 stop 之间的时间越长,火焰图样本越多。短时间采样(<10 秒)可能不够有代表性,建议 30 秒以上。
10. redefine:热修复(最后手段)
线上发现一个空指针异常,代码里只是少了一行 null 判断,但重新发版要等审批+CI+部署半小时。redefine 可以把编译好的 class 文件直接替换到 JVM 中:
bash
# 编译修复后的 class 文件
# 用 jad 反编译,修改后 javac 编译
redefine /tmp/OrderService.class限制非常大:
- 不支持新增字段或方法
- 不支持修改类结构(比如加一个内部类)
- 修改的方法体不能有修改前的 lambda 或匿名内部类引用
- 重启后变更失效
所以 redefine 只适合临时止血,正式的修复仍要走发版流程。更稳妥的替代方案是:用 watch + ognl 修改对象状态,或者用 tt 回放验证根因,然后发版修复。
案例一:接口 RT 突增,trace 到某 SQL 慢
现象:/order/list 接口 TP99 从 50ms 跳到 800ms。
排查:
dashboard— 看到 CPU 不高,但某个 http-nio 线程状态是RUNNABLE,持续占 30% CPU。thread 43— 线程栈停在java.net.SocketInputStream.socketRead0,说明在做网络 IO 读取。trace com.example.controller.OrderController listOrders— 耗时分布显示 780ms 花在OrderMapper.selectByUserId上,其中 750ms 在PreparedStatement.executeQuery。- 到数据库查慢查询日志,发现该 SQL 的
user_id字段没有索引,全表扫描。
修复:加索引,走正常发版流程。trace 在这里的作用是把"接口慢"这个模糊症状,精准定位到"某条 SQL 执行慢",节省了逐层猜疑的时间。
案例二:watch 抓到"不该出现的异常入参"
现象:订单流转到某个状态后,间歇性报 IllegalStateException,但日志只打了 orderId 和 目标状态,没有入参的完整上下文。
排查:
watch com.example.order.StateMachine transition "{params, throwExp}" -e -n 5— 监听抛异常的调用。- 等异常复现后,看到
params[0](订单对象)的status字段是"PAID",但params[1](目标状态)是"CANCELLED",状态机不允许从PAID到CANCELLED的直接跳转。 - 顺着调用链查到上游调用方传错了目标状态——是前端传参时,某个旧版本客户端把
"REFUND"传成了"CANCELLED"。
修复:前端修复版本号判断,后端加一层入参校验兜底。
生产使用注意事项
- 权限:Arthas 需要
ptrace权限(容器里默认有),redefine甚至需要-XX:+AllowEnhancedClassRedefinition参数。生产环境应该通过运维平台控制 Arthas 的访问,而不是让每个开发者都能 SSH 上去操作。 - 性能开销:
watch和trace在目标方法被高频调用(如 QPS 1000+)时,观察本身会引入额外开销。建议在低峰期使用,或通过-n限制抓取次数。dashboard基本无开销,profiler采样开销约 1-3%。 - 不要长时间挂观察:
watch没设-n的情况下,在高频方法上几分钟就能打印几万行输出,Arthas 的客户端和服务端之间的通信也有内存开销。用完就shutdown或stop观察。 - Arthas 不能做什么:跨进程调用链追踪(需要 SkyWalking 等 APM 工具)、长时间历史数据积累(需要 JFR)、数据库级别的慢查询分析(需要数据库层工具)。
bash
# 退出 Arthas(不会断开代理,只是退出客户端)
exit
# 完全停止 Arthas 代理
stop总结
Arthas 的不可替代性在于:它是唯一一个能在 JVM 进程活着的时候,把"方法调用"这个级别的运行时信息实时展示出来的工具。JFR 事后分析更全,但无法回放一个方法的具体入参;jstack/jmap 更轻量,但只能看快照;APM 工具能看到调用链和 RT,但看不到具体参数值。
十个命令从全貌到细节、从观测到干预,基本覆盖了线上诊断的常见场景。建议把上面的命令表存一份,遇到问题直接按场景查表——省得 SSH 上去后还要翻文档。
参考
- Arthas 官方文档
- Arthas 源码
- JFR + JMC 生产诊断 — 同系列文章,与 Arthas 互补的事后分析工具
- 生产诊断工具箱:从 jmap 到 JFR — 工具全景对比