外观
Arthas 在线诊断实战
Arthas 的价值是不改代码、不重启就能看到方法级的真实行为。jstack 给你线程快照,Arthas 给你"这个参数此刻传的是什么值"。
一、启动
bash
mkdir -p /opt/arthas && cd /opt/arthas
wget https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar输出:
text
[INFO] arthas-boot version: 3.7.2
[INFO] Found available processes:
* [1]: 21874 com.bfcube.order.OrderApplication
[2]: 22155 sun.tools.jps.Jps
Please select an existing java process, or input the pid: 1解释:必须用启动该 Java 进程的同一用户执行,否则 attach 失败(报 Unable to open socket file)。生产机器不能直连外网时,本地下载后 scp 上去。
退出用 stop(彻底卸载,推荐)而不是 quit(只断会话,字节码增强还挂在进程里)。
诊断完必须 stop
Arthas 靠字节码增强工作,quit 之后增强仍在,会持续消耗 CPU 且可能影响 JIT。生产用完一定 stop,或 shutdown 彻底清理。
二、看全局:dashboard
text
Memory used total max usage
heap 3.2G 4.0G 4.0G 80.00%
g1_old_gen 2.1G 2.6G 4.0G 52.50%
g1_eden_space 0.9G 1.2G 1.2G 75.00%
Threads Total: 214, NEW: 0, RUNNABLE: 38, BLOCKED: 4, WAITING: 120, TIMED_WAITING: 52
ID NAME GROUP PRI STATE %CPU DELTA_TIME TIME INTERRUPTED DAEMON
152 order-pool-3 main 5 RUNNABLE 99.3 0.099 312:10 false false解释:一屏看完内存分区 + 线程状态分布 + 按 CPU 排序的线程表。第 152 行那个 99.3% 的线程,直接 thread 152 就能看它的栈——比 top -Hp + 十六进制换算那套流程快得多。
三、线程:thread
bash
thread 152 # 看指定线程栈
thread -n 3 # CPU 最高的 3 个线程
thread -b # 直接找出互相持锁的死锁线程thread -b 输出:
text
Found one Java thread deadlock:
"pool-2-thread-1" Id=141 WAITING on java.lang.Object@7c0012340
- locked java.lang.Object@7c0012348
- waiting to lock monitor 0x00007f3c12ab (object 0x7c0012340)
at com.bfcube.svc.TransferService.transferOut(TransferService.java:64)解释:thread -b 是死锁一键定位,比翻 jstack 文件快一个数量级。生产上遇到"接口大面积超时但 CPU 很低",第一步就该跑它。
四、看方法入参出参:watch
bash
watch com.bfcube.dao.OrderDao selectById '{params, returnObj, throwExp}' -x 2 -n 5输出:
text
method=com.bfcube.dao.OrderDao.selectById location=AtExit
ts=2026-10-27 16:31:22; [cost=184.33ms] result=@ArrayList[
@Object[][
@Long[20261027001],
],
@Order[com.bfcube.model.Order@3f2a11],
null,
]解释:
params入参数组、returnObj返回值、throwExp异常,三者按需组合。-x 2是展开深度,默认 1 只能看到对象地址,2~3 才能看到字段值。-n 5只抓 5 次就停——必须加,不加会在高 QPS 接口上把日志刷爆并显著拖慢业务。location=AtExit表示方法正常返回时观测;异常场景用AtExceptionExit。
只看耗时长的调用:
bash
watch com.bfcube.svc.OrderService sync '{params, #cost}' '#cost > 500' -n 10五、看调用链路耗时:trace
bash
trace com.bfcube.svc.OrderService sync '#cost > 300' -n 5输出:
text
`---ts=2026-10-27 16:34:11;thread_name=order-pool-7;cost=1284.22ms
`---[1284.221ms] com.bfcube.svc.OrderService:sync()
+---[3.112ms] com.bfcube.dao.OrderDao:selectById()
+---[1204.887ms] com.bfcube.client.StockClient:lock() ← 这里
`---[72.441ms] com.bfcube.dao.OrderDao:updateStatus()解释:一行一个调用层级,直接指出 1.2 秒花在 StockClient.lock()。慢接口定位的主力命令,比加日志重新发布快得多。
配合 skipJDKMethod false 可以看到 JDK 内部调用;--skipJDKMethod(默认 true)能减少噪音。
六、反编译线上代码:jad
bash
jad --source-only com.bfcube.util.PriceCalculator > /tmp/PriceCalculator.java解释:确认"线上跑的到底是不是我以为的那个版本"。发布后行为诡异时,jad 看一眼比查 CI 日志直接。
七、热更新(应急用)
bash
mc /tmp/PriceCalculator.java -d /tmp/redefine # 内存编译成 class
redefine /tmp/redefine/com/bfcube/util/PriceCalculator.class解释:只能改方法体,不能增删字段和方法(JVM 类结构限制)。适合"线上有个明显的判断条件写错、发版要走 40 分钟流程"的应急场景。
热更新是双刃剑
redefine 的改动不落盘,重启即失效,且容易造成"线上代码和仓库不一致"的排查黑洞。用完必须:① 立刻提交对应修复并走正常发布;② 在变更群里明确广播;③ 生产环境建议直接禁用该能力(启动时不带 disabled-commands=redefine 的相反操作)。
八、日志与火焰图
bash
logger --name ROOT --level debug # 运行时改日志级别,不用重启
profiler start --event cpu
profiler stop --file /tmp/arthas.html # 生成火焰图解释:profiler 基于 async-profiler,采样开销极低,比 perf 更适合 Java(能正确展开 JIT 栈)。火焰图里"宽而平"的顶就是热点。
九、常用命令速查
| 命令 | 用途 |
|---|---|
dashboard | 内存 + 线程总览 |
thread -n 3 / thread -b | CPU 最高线程 / 死锁 |
watch | 看入参、返回值、耗时、异常 |
trace | 方法内部调用链耗时 |
stack | 这个方法被谁调用(反向找调用方) |
monitor -c 5 | 5 秒窗口统计调用量、成功率、RT |
tt | 时空隧道:记录多次调用,事后回放 |
jad | 反编译 |
sc -d / sm | 查类是否加载、由哪个 ClassLoader 加载 |
vmtool --action getInstances | 从堆里直接取某类的实例并调方法 |
heapdump | 等价 jmap dump |
sc -d 的用途
类冲突排查(同名类被不同 ClassLoader 加载)时,sc -d com.xxx.Foo 能看到 classLoaderHash 和 code source,一眼看出 jar 是从哪个路径加载的。