给 Java 开发者的线上诊断通关攻略 Arthas
你有没有遇到过这种情况:线上接口突然变慢,本地复现不了;CPU 莫名飙到 100%,看了半天日志也没头绪;或者某个方法返回值不对,但日志根本没打印入参。
以前遇到这些事,要么加日志重新发版,要么用 jstack、jmap 凑合看一眼。但这套流程又慢又粗糙,等发版排完,问题可能自己都消失了。
Arthas 就是来解决这个问题的。它直接 attach 到运行中的 JVM 上,让你看到方法入参、返回值、调用链路耗时,甚至能反编译运行中的代码、热更新修 Bug——全程不用重启应用。
这篇整理一下我日常用 Arthas 的完整流程,从安装到实战,一条龙走完。
先说环境
Arthas 本身是 Java 写的,先确认机器上有 JDK:
java -version
JDK 8 以上就行,8 到 21 都支持。如果你的目标应用跑在 JDK 6/7 上,得用 Arthas 3.x 的版本。
然后得有一个正在跑的 Java 进程。没有的话,可以用官方的演示程序:
curl -O https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar
这个程序会在终端不断打印类似 12345=3*5*823 的东西,说明它活着。
安装
就一行命令:
curl -O https://arthas.aliyun.com/arthas-boot.jar
下载慢的话加个阿里云镜像:
java -jar arthas-boot.jar --repo-mirror aliyun
如果你是 Linux 服务器,也可以用脚本一键装:
curl -L https://arthas.aliyun.com/install.sh | sh
装完会生成一个 as.sh,直接 ./as.sh 就能启动。
启动并连接到 Java 进程
java -jar arthas-boot.jar
它会列出当前所有的 Java 进程:
* [1]: 35542
[2]: 71560 math-game.jar
输入序号回车就行。如果已经知道 PID,也可以直接带上:
java -jar arthas-boot.jar 71560
连接成功后会弹出一个 ASCII Art Logo,看到它就说明 attach 上了。
有个坑提一下:启动 Arthas 的用户必须和目标进程是同一个用户,不然 attach 不上。如果目标进程是 admin 跑的:
sudo -u admin -EH java -jar arthas-boot.jar
attach 失败的话,去看 ~/logs/arthas/ 下的日志。
核心命令
进入 Arthas 交互界面后,先用 help 看看有哪些命令。我这里不列全量文档,只说日常最常用的那几个。
dashboard:先看一眼全局
dashboard
相当于 Linux 的 top,一屏展示线程状态、内存使用、GC 情况。Ctrl+C 退出刷新。
第一眼看什么?线程的 %CPU 列有没有特别高的,内存的 usage 有没有快满的,GC 次数有没有暴涨。先建立个整体印象,再往下细查。
thread:CPU 飙高就靠这个
排查 CPU 问题,第一步永远是这个:
thread -n 3
打印 CPU 占用最高的 3 个线程,带完整堆栈。看完堆栈基本就知道是哪行代码在烧 CPU 了。
如果应用卡死了不响应,试试这个:
thread -b
它会直接帮你找出那个持着锁、阻塞了其他线程的"罪魁祸首"。这个命令在排查死锁和阻塞问题时非常好用。
想看某个具体线程的堆栈:
thread 1
按状态过滤:
thread --state BLOCKED
采样间隔可以调大一点,结果更准(默认 200ms,可以设成 1000ms):
thread -n 3 -i 1000
jad:反编译运行中的代码
有时候怀疑线上跑的不是最新代码,或者根本拿不到源码,直接反编译:
jad com.example.MyController
只看某个方法:
jad com.example.MyController getUser
只输出源码、不要 ClassLoader 信息:
jad --source-only com.example.MyController
反编译出来的代码带语法高亮,阅读体验还不错。如果发现一个类被多个 ClassLoader 加载了,它会列出所有的 hashcode,用 -c 指定具体哪个就行。
sc / sm:搜类和搜方法
确认某个类有没有被加载:
sc com.example.*
看详细信息(从哪个 jar 加载的、ClassLoader 是什么):
sc -d com.example.MyController
连成员变量一起看:
sc -d -f com.example.MyController
看类里有哪些方法:
sm com.example.MyController
小技巧:异常堆栈里的类名是 com/example/MyController 这种斜杠分隔的,直接丢给 sc 就行,它会自动识别,不用手动替换成点号。
watch:看方法的入参和返回值
这大概是 Arthas 里用得最多的命令了。你想知道某个方法被调用时传了什么参数、返回了什么,一行搞定:
watch com.example.MyController getUser '{params, returnObj}'
-x 控制展开深度,默认只展开 1 层,对象内部的内容看不到。设成 2 就能看到字段值了:
watch com.example.MyController getUser '{params, returnObj}' -x 2
只看返回值:
watch com.example.MyController getUser returnObj
加条件过滤,比如只看耗时超过 200ms 的调用:
watch com.example.MyService processData '{params, returnObj}' '#cost > 200'
只看抛异常的情况:
watch com.example.MyService processData '{params[0], throwExp}' -e -x 2
看当前对象(this)的属性:
watch com.example.MyService processData 'target.someField'
在方法执行前观察(此时还没有返回值):
watch com.example.MyController getUser '{params}' -x 2 -b
-n 2 可以限制只执行两次就退出,方便快速验证:
watch com.example.MyController getUser '{params, target, returnObj}' -x 2 -n 2
trace:方法内部每一步耗时多少
watch 看的是入参返回值,trace 看的是方法内部调了哪些子方法、各耗时多少。排查慢接口的时候非常有用。
trace com.example.MyController getUser
只跑一次就退出:
trace com.example.MyController getUser -n 1
只看总耗时超过 10ms 的:
trace com.example.MyController getUser '#cost > 10'
默认不追踪 JDK 内部方法,想看的话加上:
trace --skipJDKMethod false com.example.MyController getUser
用正则匹配多个类和方法:
trace -E com.example.ClassA|com.example.ClassB method1|method2|method3
需要注意:trace 默认只追踪一层子调用。想深入多层的话,3.3.0 之后支持动态 trace——在另一个终端 telnet localhost 3658 连上来,用 --listenerId 指定就能继续往下钻。
stack:这个方法到底是被谁调用的
有时候一个方法被执行了,但不知道是谁触发的。stack 打印调用路径:
stack com.example.MyService processRequest
加条件:
stack com.example.MyService processRequest 'params[0] < 0'
限制次数:
stack com.example.MyService processRequest -n 2
monitor:统计方法调用情况
看一段时间内某方法的调用次数、成功失败次数、平均响应时间:
monitor com.example.MyController getUser -c 10
-c 10 表示每 10 秒统计一次。加条件也行:
monitor com.example.MyController getUser 'params[0] > 100' -c 10
注意 monitor 是通过字节码增强实现的,用完记得 reset 或 stop 还原。
logger:不重启改日志级别
线上出问题了想看 debug 日志,但应用配的是 info,又不能重启。改一下:
logger --name com.example --level debug
排查完改回去:
logger --name com.example --level info
整个操作几秒钟,全程不重启。这个命令我在生产环境用过很多次了,非常稳。
vmtool:翻内存里的对象 & 强制 GC
查内存里某个类型有几个实例:
vmtool --action getInstances --className com.example.MyController --limit 5 -x 2
强制 Full GC:
vmtool --action forceGc
heapdump:导出堆快照
OOM 排查必备,类似 jmap -dump:
heapdump --live /tmp/heapdump.hprof
--live 只导出可达对象,文件更小。导出来用 MAT 或 JProfiler 打开分析。
profiler:生成火焰图
CPU 性能问题的终极武器。先开始采样:
profiler start
等个 30 秒左右,停掉并生成火焰图:
profiler stop --file /tmp/flame.html
用浏览器打开这个 HTML,横轴越宽表示这个方法被采样到的次数越多(越耗 CPU),纵轴是调用栈深度。一眼就能看出瓶颈在哪。
ognl:直接执行表达式
调用静态方法:
ognl '@java.lang.System@currentTimeMillis()'
获取静态变量:
ognl '@com.example.MyConfig@SOME_CONSTANT'
甚至能改私有字段(慎用):
ognl '#target = @com.example.MyConfig@INSTANCE, #target.secretKey = "new-value"'
sysprop / sysenv
查看和修改 JVM 系统属性、环境变量。查看所有:
sysprop
查看某个:
sysprop java.version
临时改一个(重启后失效):
sysprop my.custom.property newValue
热更新代码
这个功能我觉得是 Arthas 最酷的地方。线上发现一个 Bug,不想等发版流程,可以直接热更新修复。
基本流程是这样的:
第一步,反编译目标类,拿到当前运行中的源码:
jad --source-only com.example.BugController > /tmp/BugController.java
第二步,编辑这个文件,修掉 Bug:
vi /tmp/BugController.java
第三步,内存编译成 .class:
mc /tmp/BugController.java -d /tmp
第四步,加载到 JVM:
redefine /tmp/com/example/BugController.class
第五步,验证一下:
jad com.example.BugController
watch com.example.BugController buggyMethod returnObj
几个注意点:热更新是临时的,应用重启就恢复了,所以正式发版不能省。另外只能改方法体,不能加字段加方法。线上操作前务必先在测试环境跑一遍。
几个实战场景
把上面这些命令串起来,看看实际遇到问题时的排查思路。
CPU 飙到 100%
java -jar arthas-boot.jar
thread -n 3
看堆栈定位到具体代码。如果一层不够,用 trace 往下钻:
trace com.example.YourClass yourMethod
还不行就上火焰图:
profiler start
# 等 30 秒
profiler stop --file /tmp/cpu-flame.html
接口卡死不响应
thread -b
先找出是谁拿着锁。然后看 BLOCKED 的线程:
thread --state BLOCKED
拿到线程 ID 后看完整堆栈:
thread 27
再用 trace 看卡在哪一步:
trace com.example.YourService yourMethod
方法返回值不对
watch com.example.YourService yourMethod '{params, returnObj}' -x 2
看看每次调用的入参和返回值是什么。怀疑异常的话:
watch com.example.YourService yourMethod '{params[0], throwExp}' -e -x 2
不确定代码是不是最新的
jad --source-only com.example.YourController
sc -d com.example.YourController
看 code-source 字段,就是 jar 包路径,确认是不是预期的版本。
OOM 内存泄漏
heapdump --live /tmp/heapdump.hprof
导出来用 MAT 分析。或者先看看内存里某个对象有多少实例:
vmtool --action getInstances --className com.example.YourObject --limit 100
memory
偶发问题,一天才出一次
用后台任务挂着:
watch com.example.YourService rareMethod '{params, returnObj}' -x 2 &
jobs 查看后台任务,fg <job_id> 拉到前台,kill <job_id> 终止。后台任务生命周期默认 1 天,断开 session 也不影响。
退出和清理
退出当前连接(Arthas 服务端还在跑):
quit
完全停止 Arthas(所有增强过的类会被还原):
stop
只还原增强过的类,不退出 Arthas:
reset com.example.MyController
reset *
彻底卸载:
rm -rf ~/.arthas/
rm -rf ~/logs/arthas/
几个踩过的坑
- 权限:启动 Arthas 的用户和目标进程必须是同一个用户,不然 attach 不上。
- 性能开销:watch、trace、monitor 这些命令通过字节码增强实现,有性能开销。用的时候指定到具体的类和方法,别用太宽的通配符。查完就
reset或stop。 - 热更新非持久:redefine 的改动重启就没了,该发版还得发版。
- 端口安全:Arthas 默认监听 3658 端口,生产环境注意别暴露到公网,可以配 auth 鉴权。
- trace 只有一层:默认只追踪一层子调用,多层需要用动态 trace。
- 采样精度:thread 的 CPU 采样有自身开销,间隔太短不准,建议设到 1000ms 以上。
快捷键
| 快捷键 | 功能 |
|---|---|
Ctrl+C |
中断当前命令 |
Ctrl+D |
退出(等同 quit) |
Tab |
自动补全 |
↑ ↓ |
浏览历史命令 |
Ctrl+R |
搜索历史命令 |
写在最后
Arthas 用熟了之后,很多以前需要重启应用才能排查的问题,现在几条命令就搞定了。尤其 watch + trace + thread 这三个组合,覆盖了日常 80% 的诊断场景。
建议平时拿官方的 math-game 演示程序练练手,等线上真出问题的时候不至于手忙脚乱。
官方文档在这里,遇到具体参数问题随时查:https://arthas.aliyun.com/doc

浙公网安备 33010602011771号