大家好,我是程序员天天困。
这是我的「Arthas 线上诊断实战」系列第 1 篇,整个系列计划写 8 篇,覆盖 watch、trace、vmtool、profiler、ognl 这些常用命令,最后落到字节码增强原理。想看系列的可以点个关注,断更不弃更。
假如线上一个接口返回值莫名其妙偏大,本地跑一遍完全复现不了。按老办法只能加日志、走测试、再灰度——一轮下来半天没了,问题还可能因为重启就消失。最好的办法是用 Arthas,一条 watch 命令贴上去,三分钟就能定位到问题
如果你也经常被「线上没日志、本地复现不了、又不能随便重启」这三件事卡住,这篇就把 Arthas 的入门姿势一次讲清楚:它是什么、怎么装、watch 命令怎么抓出入参。点个收藏,我们开始
一、Arthas 是什么
官方对它的定义是:
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
——Arthas 官方文档·简介

Arthas(阿尔萨斯):Alibaba 开源的 Java 线上诊断工具,能在不重启 JVM、不改业务代码的前提下观察方法的出入参、异常、耗时和运行时状态。你可以把它理解为「给运行中的 JVM 外接一个仪表盘,不动它的内部运转就能看到哪不对」。
它是 Alibaba 开源的 Java 诊断工具,深受开发者喜爱。当你遇到下面这些束手无策的问题时,它能帮你解决:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到 JVM 的实时运行状态?
- 怎么快速定位应用的热点,生成火焰图?
- 怎样直接从 JVM 内查找某个类的实例?
Arthas 4.x 支持 JDK 8+,包括 JDK 17、JDK 21 和 JDK 25,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
我自己的体感是,Arthas 对于 Java 后端,就像电工师傅手里的电笔——线上有没有问题、问题落在哪个方法,一搭就知道,不必为了排查把服务停了或重启。这个系列我会一篇一篇把常用命令过一遍,第一篇就从最常用的 watch 开始。

二、为什么需要 Arthas
本地开发环境访问不到生产环境,生产出了问题没法用 IDE 远程调试——何况调试会暂停所有线程,等于让服务停摆。想在测试或预发环境复现?有些问题跟环境强相关,复现不了,甚至一重启就消失。加日志排查又得走「测试→预发→生产」一整套,等发上去 JVM 一重启,现场可能就没了。
Arthas 解决的就是这个困局:在线解决生产问题,既不重启 JVM,也不改业务代码;以观察者身份介入,不暂停任何线程。
三、动手前的准备:装 IDEA 插件
在用之前,先装一个 IDEA 插件,帮我们快速生成 Arthas 命令,省得手敲一长串类名方法名。
打开 IDEA 的 Settings → Plugins → Marketplace,搜 Arthas 即可找到。

装完之后,在任意一个方法上右键,菜单里会多出一组 Arthas 命令(watch、trace、stack 等),点一下就把对应命令复制到剪贴板。

四、启动 Arthas 并 Attach 到目标进程
插件装好,接下来启动 Arthas 本体。它就是一个可执行 jar,两行命令搞定:
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
启动之后,Arthas 会先检查当前机器上有哪些 Java 进程,列成一个带序号的清单:
Attach(附加连接):Arthas 通过 JVM 自带的 Attach API 把自己「挂」到目标 Java 进程上,注入一段诊断代码,从而观察方法调用。它走的是观察者模式,不会暂停线程,所以能在生产环境安全使用。你可以理解为「给运行中的 JVM 外接一个仪表盘,不动它的内部运转」。

直接输入目标进程对应的序号回车,Arthas 就会 Attach 上去:

Attach 成功后,命令行提示符会变成 [arthas@PID]$,意味着你已经在目标进程里了,可以开始下诊断命令。
五、watch 命令实战:揪出那个多 +1 的 bug
接下来用一个实际例子看 watch 到底怎么用。先看一段最简单的程序:
@RequestMapping(path = "/user/calculate", method = RequestMethod.GET)
public Integer calculate(@RequestParam Integer x,@RequestParam Integer y,@RequestParam(name = "op") String op) {return doCalculate(x, y, op) + 1; // 玄机就在这一行
}public int doCalculate(int x, int y, String op) {if ("add".equals(op)) return x + y;else if ("sub".equals(op)) return x - y;else if ("mul".equals(op)) return x * y;else if ("div".equals(op)) return x / y;else throw new UnsupportedOperationException("NotSupportOp" + op);
}
这个接口算 x 和 y 的运算结果。注意 calculate 第 5 行有个 bug:把 doCalculate 的结果又 +1 了。从调用方看结果就是错的,但逻辑一长,肉眼一行行看代码很难一眼定位。
用 Arthas,我们可以动态观察 doCalculate 的返回值,一步一步排查,根本不用改代码。
watch(方法观察命令):Arthas 的核心命令之一,在目标方法每次被调用时拦截,按你给的表达式打印入参、返回值或异常。你可以理解为「在方法门口装了个摄像头,谁进谁出、带了什么、摔没摔跤,全拍下来」。
在 IDEA 里对 doCalculate 方法右键 → Arthas → watch,会复制出这样一条命令:
复制出来的命令长这样:
watch com.ttk.controller.WebController doCalculate '{params,returnObj,throwExp}' -n 5 -x 3
这条命令我详细拆开讲,每一段都别记错:
watch—— 表示当前要用的命令是 watch。org.example.controller.WebController—— 目标类的全限定名。doCalculate—— 目标方法名。'{params,returnObj,throwExp}'—— 要观察的对象,用 OGNL 表达式包起来:params对应方法的入参集合;returnObj对应方法走完后的返回结果;throwExp对应方法抛出的异常对象。
合起来就是对这个方法的出入参和异常一起做诊断。
-n 5—— 诊断的次数,比如只观察 5 次调用就自动退出。-x 3—— 输出对象时展开的层级,这个不太好理解,后面单独用例子讲。
把这条命令复制到 Arthas 终端:

然后触发一次调用:
curl 'http://localhost:8080/user/calculate?x=1&y=2&op=add'

可以看到入参 x=1、y=2、op=add,返回值是 3,说明 doCalculate 本身的计算没问题;最后一个是 null,表示异常为 null,也就是没发生异常。
这时候我们再去 watch 外层的 calculate 方法:
watch com.ttk.controller.WebController calculate '{params,returnObj,throwExp}' -n 5 -x 3
就会发现返回值赫然是 4。两边一对比,问题就在 calculate 多写的那个 +1 上——这样很快就能找到问题所在,没动一行业务代码。
六、异常观察:throwExp 不再是 null
上面我们看到,如果方法抛了异常,watch 也是能观察到的。
比如把请求的 op 换成 add2,doCalculate 就会抛出 UnsupportedOperationException:

这时候再 watch,你会发现第三个对象 throwExp 不再是 null 了,而是对应的异常对象加完整的堆栈信息。这就是 throwExp 的意义——正常时是 null,抛异常时给你完整的异常现场,不用再去翻日志。

七、-x 的用法:看穿嵌套 Map
回到最开始那条命令里的 -x:
watch com.ttk.controller.WebController calculate '{params,returnObj,throwExp}' -n 5 -x 3
-x 表示展开的层级,直接讲不好理解,看个例子就懂了。假如有个参数嵌套很深的方法:
public Integer doCalculate2(Map<String, Map<Integer, Set<Integer>>> data) {for (Map.Entry<String, Map<Integer, Set<Integer>>> entry : data.entrySet()) {String op = entry.getKey();Map<Integer, Set<Integer>> value = entry.getValue();Map.Entry<Integer, Set<Integer>> data_op = value.entrySet().stream().findAny().get();int x = data_op.getKey();int y = data_op.getValue().stream().findAny().get();return doCalculate(x, y, op);}return 0;
}
遇到这种参数嵌套好几层的,直接 watch 一下:
watch com.ttk.controller.WebController doCalculate2 '{params,returnObj,throwExp}' -n 5 -x 3
发送请求
curl 'http://localhost:8080/user/calculate2'

你会发现只能看到 Map 第一个 key 是啥,里面套的 Map 和 Set 全看不出来。这时候把 -x 调成 4:

就能看到前两层了,以此类推,-x 调得越大展开越深。一句话:-x 就是「你愿意看到第几层」。
八、params 的操作:只看你想看的那一个
继续回到这条命令:
watch com.ttk.controller.WebController calculate '{params,returnObj,throwExp}' -n 5 -x 3
注意这里的 params 是 param 的复数——因为函数参数有很多个。如果我们只想看其中一个呢?
比如这个方法:
public int doCalculate(int x, int y, String op) {if ("add".equals(op)) return x + y;else if ("sub".equals(op)) return x - y;else if ("mul".equals(op)) return x * y;else if ("div".equals(op)) return x / y;else throw new UnsupportedOperationException("NotSupportOp" + op);
}
我只想看 op,很简单:
watch com.ttk.controller.WebController calculate '{params[2]}' -n 5 -x 3
params 本质是个数组,索引从 0 数起,0 就是第一个参数,1 是第二个,2 就是第三个,依此类推。
甚至你还可以在 params 上做一些简单的计算操作,比如观察这两个入参的和:
watch com.ttk.controller.WebController calculate '{params[0]+params[1]}' -n 5 -x 3
这就是 watch 的用法。稍微引申一下,watch 拿到的观察对象固定就三个:
params:方法被调用时传进来的参数,按数组形式组织;returnObj:方法正常走完时返回的结果;throwExp:方法如果抛了异常,这里就是那个异常。
围绕这三个对象,你还能塞进简单的表达式做运算,比如下钻取属性、对入参做加减、把两个对象拼一起看,日常排查里八九成的场景都能覆盖。
结语
Arthas 解决的就是「线上有现场、但不能动现场」这个死结,而 watch 是它最趁手的那把螺丝刀——一行命令,出入参异常全在眼前。这一篇我们摸了 watch 的皮毛,下一篇聊 trace,看怎么定位「一个请求里到底哪一行最耗时」。
本文基于 Arthas 4.x 官方文档与个人实践整理,命令参数以官方文档为准。
我是程序员天天困,持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊:你在线上排查 bug 时,被哪个「没打日志的角落」坑过最久。

Java 线上突然报错却没打日志?本文带你用 Arthas 在不重启 JVM 的前提下,用 watch 命令实时抓方法出入参,10 分钟上手线上诊断