AIGC标识 给 Java 开发者的线上诊断通关攻略 Arthas

你有没有遇到过这种情况:线上接口突然变慢,本地复现不了;CPU 莫名飙到 100%,看了半天日志也没头绪;或者某个方法返回值不对,但日志根本没打印入参。

以前遇到这些事,要么加日志重新发版,要么用 jstackjmap 凑合看一眼。但这套流程又慢又粗糙,等发版排完,问题可能自己都消失了。

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 是通过字节码增强实现的,用完记得 resetstop 还原。

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/

几个踩过的坑

  1. 权限:启动 Arthas 的用户和目标进程必须是同一个用户,不然 attach 不上。
  2. 性能开销:watch、trace、monitor 这些命令通过字节码增强实现,有性能开销。用的时候指定到具体的类和方法,别用太宽的通配符。查完就 resetstop
  3. 热更新非持久:redefine 的改动重启就没了,该发版还得发版。
  4. 端口安全:Arthas 默认监听 3658 端口,生产环境注意别暴露到公网,可以配 auth 鉴权。
  5. trace 只有一层:默认只追踪一层子调用,多层需要用动态 trace。
  6. 采样精度:thread 的 CPU 采样有自身开销,间隔太短不准,建议设到 1000ms 以上。

快捷键

快捷键 功能
Ctrl+C 中断当前命令
Ctrl+D 退出(等同 quit)
Tab 自动补全
浏览历史命令
Ctrl+R 搜索历史命令

写在最后

Arthas 用熟了之后,很多以前需要重启应用才能排查的问题,现在几条命令就搞定了。尤其 watch + trace + thread 这三个组合,覆盖了日常 80% 的诊断场景。

建议平时拿官方的 math-game 演示程序练练手,等线上真出问题的时候不至于手忙脚乱。

官方文档在这里,遇到具体参数问题随时查:https://arthas.aliyun.com/doc

posted @ 2026-08-20 19:47  SunArmy  阅读(6)  评论(0)    收藏  举报