写 Java 的,基本都经历过这种绝望时刻:线上接口突然从 200ms 变成 2000ms,CPU 飙到 100%,日志里却只有一行没头没尾的 NPE 堆栈。你赶紧加日志、重新发布,结果问题只出现一次,换环境还复现不了。Arthas 就是为这种时刻而生的。它是一款开源的 Java 诊断工具,可以在不改代码、不重启应用的前提下,附着到正在运行的 JVM 上,实时查看类加载情况、方法的参数返回和异常、线程执行栈、内存与 GC 状态,甚至直接反编译线上代码、临时调整部分运行逻辑。它的核心价值就是把你从“猜”变成“看”,把线上问题的定位时间从几小时缩短到几分钟。这篇文章写给所有 Java 后端开发、运维和 SRE 岗位的朋友。我会以使用者的视角,把 Arthas 的核心功能、实操方法、生产环境避坑经验一次性讲透,也欢迎刚入行的同学照着操作。
1. 为什么需要 Arthas:线上问题诊断的真实痛点
1.1 传统排查手段为什么不行
在 Arthas 普及之前,线上问题排查基本靠三板斧:加日志、看监控、连 debug 远程调试。这三招看起来都有用,实际用起来各有各的尴尬。
先说加日志。改代码、构建、发布、重启,最小周期也要好几分钟,很多时候要申请发布窗口,一个下午就这么没了。更麻烦的是,有些问题根本不能稳定复现,你加了日志,它偏偏不出现,日志打印得再多也没意义。我遇到过最典型的案例:一个偶发性的空指针,一周出现两三次,每次都在凌晨低峰期,等日志加上去之后连续安静了半个月,根本没法验证。
再说监控平台。Prometheus、Grafana 这类工具能告诉你 CPU 高、内存涨、接口 RT 变长,但它只能回答“哪里不对劲”,回答不了“那个方法里到底传了什么参数”“线程为什么卡住”。监控是宏观视角,相当于天气预报告诉你明天有雨,但不会告诉你哪滴雨会砸到你头上。你要定位微观问题,还是缺一把手术刀。
远程调试就更不用说了,debug 模式一旦开启断点,对应线程直接挂起,生产环境几十万的 QPS 根本扛不住这种暂停。就算你敢在测试环境挂 debug,很多线上问题只有在特定流量和特定数据下才会触发,测试环境复现不出来,你挂一天断点也等不到那个瞬间。加上灰度发布、容器化之后,环境差异越来越大,传统手段越来越使不上劲。
1.2 Arthas 解决的到底是什么问题
Arthas 解决的核心矛盾是:线上 JVM 对开发者不透明问题。过去,运行中的 Java 应用就像一只黑箱,你只能从箱子外面看到冒烟了、变烫了,但看不到箱子里面哪根线路在打火。Arthas 本质上就是给这个黑箱加了一扇观察窗。
这个能力的底层依赖是 JDK 自带的两个机制:attach 和 instrumentation。attach 允许外部进程连接到正在运行的 JVM 并加载 agent 程序,instrumentation 允许这个 agent 修改已加载类的字节码,实现对方法调用的动态拦截。Arthas 把这两套底层能力封装成一套交互式命令行,你不需要理解字节码细节,只需要敲命令就能拿到方法级的信息。
不需要改代码、不需要重启、不需要加埋点,这三个“不需要”是它最大的价值。试想一下,生产环境出了问题,你可以像看本地 IDE 调试器一样看线上方法入参出参,还能看到复杂对象的内部字段,这是过去完全不敢想象的事。而且 Arthas 对目标进程的影响很小,默认情况下只有执行观察命令时才会对方法做字节码增强,命令停止后增强就会卸载,业务逻辑本身不受影响。
1.3 谁最应该掌握这个工具
如果你是后端开发,Arthas 能直接帮你解决“代码看着没问题但线上就是不对”的困境。我之前带过的一个新人,第一次独立排查线上接口超时,用 trace 命令看方法耗时分布,不到十分钟就定位到是一个外部缓存读操作异常。这个效率在以前是不可想象的。
如果你是运维或 SRE,Arthas 的价值就更直接了。你不需要在 Java 代码层面理解全部业务,就能通过 dashboard 看线程状态、通过 thread 命令抓 CPU 高线程、通过 jad 对比线上 class 是否符合预期。平台监控发现问题,Arthas 负责单机单进程的微观取证,正好补上监控的盲区。
如果你是测试或者刚入门的 Java 开发,也不要觉得这个工具离你很远。Arthas 的命令是交互式的,自带补全和帮助,上手成本很低。你可以在自己的本地服务上跑一跑 dashboard、watch、trace,用最简单的 demo 理解 JVM 运行时的行为。这些经验将来处理线上问题时会直接派上用场。
1.4 什么场景下不适合用 Arthas
先说结论:Arthas 是排查工具,不是常驻服务,更不是万能药。
进程已经崩溃,或者内存溢出到无法响应,Arthas 就没法 attach 了。这种情况需要先有可复现的环境,把进程拉起来再观察,或者直接靠 heap dump 分析。还有一种场景是容器环境限制严格,比如安全策略禁止动态附着、禁止访问 /proc,或者容器完全没有权限,这时候 Arthas 跑不起来,你需要先在平台侧确认放行权限。最后我特别想提醒:不要在业务高峰期长时间跑高频观察命令,尤其是 watch 不带次数限制,在高 QPS 接口上会造成明显的性能损耗。工具无罪,但用错场景就是给自己挖坑。
2. 核心功能全景拆解:一条命令一块能力
2.1 dashboard:一眼看透 JVM 实时状态
进入 Arthas 后,我习惯敲的第一条命令永远是 dashboard。这条命令会打开一个实时刷新的面板,从上到下依次展示 JVM 信息、线程列表、内存分区、GC 次数和类加载统计。
线程列表是最值钱的部分。每一行代表一个线程,包含线程 ID、名称、状态、CPU 占用率。这里有一个点要注意:CPU 占用率是采样估算值,不是精确统计,别对着小数点后面几位较劲,把它当成快速筛选方向的指标就够用了。如果某个线程 CPU 占用一直在 80% 以上,后面就可以用 thread 命令深挖这个线程的堆栈。
内存区域会显示 eden、survivor、old 各个分区的使用情况,以及最近 GC 次数和耗时。如果你发现 old 区占用接近 100%,而且 Full GC 次数不断增长,那基本可以断定是内存压力问题,下一步就该考虑是对象泄漏还是配置不当。dashboard 还有一个用途是类加载统计,可以看到当前 JVM 已加载的类数量,这个数字如果异常偏高,多半和动态生成代理类或反射使用过多有关。
dashboard 面板默认持续刷新,退出直接按 q 或者 Ctrl+C。我见过有人按 Esc 半天没反应,以为死机了,其实都是没找到正确的退出键。
2.2 类排查三板斧:sc / sm / jad
这三个命令帮你回答同一个问题:线上跑的到底是谁?代码是什么样的?
sc 是搜索类,你可以用通配符查所有已加载的类。最常配合的做法是sc *UserService,会列出所有名字匹配 UserService 的类。加-d参数可以输出详细类信息,包括类加载器、代码来源、注解、以及继承关系。这个命令最实用的场景是确认线上加载的 class 到底来自哪个 jar、哪个版本。有一次我排查一个奇怪的现象,代码里明明改了逻辑但线上行为没变,sc -d 一看,来源文件还是三天前的旧构建产物。那一刻我真的是长舒一口气,至少证明不是工具问题,是发布问题。
sm 是搜索方法,用来获取方法签名、修饰符和抛出的异常。你准备对某个方法做 watch 或 trace 时,先跑一遍 sm 确认方法名和参数类型,能避免很多签名写错导致的无效命令。
jad 是反编译字节码。执行jad com.example.UserService可以直接把线上的 class 还原成接近源码的 Java 代码。它特别适合在脑子里确认“线上这段逻辑到底和我本地代码一不一样”。需要提醒的是,反编译出来的代码没有注释,泛型信息可能被擦除,某些复杂的语法结构会变形,别拿它和源码逐字对比,能看逻辑就可以了。
2.3 运行时观测三件套:watch / trace / stack
这套组合拳是 Arthas 真正的灵魂,也是使用频率最高的三个命令。
watch 负责观察方法的入参、返回、异常和耗时。常规写法是watch 类名 方法名 '{params, returnObj, throwExp}' -x 2 -n 5。这里有两个参数非常重要:-x控制对象展开深度,默认是 1,如果你要看一个多层嵌套对象里面的字段,建议设到 2 或 3;但是别狂往大里调,-x 5在复杂对象上可能输出几千行,直接把终端刷爆。-n控制观察几次后结束,这个必须加,后面我还会再强调。
trace 是耗时分布分析器。它会打印方法内部的调用树,每个子方法后面会标出总耗时和自身耗时。接口慢但是不知道慢在哪里时,trace 就是最直接的拆解工具。你执行trace com.example.OrderService createOrder '#cost > 500',就能把耗时超过 500ms 的调用链全部列出来,一眼看到是哪个子方法吃掉了大头。
stack 是打印当前方法被调用的完整调用栈。当你发现某个异常方法被频繁触发,你想知道它是从哪条业务链路进来的,stack 直接给你答案。它可以帮助你在方法层面建立调用关系的上下文,尤其是面对那种被多个入口调用的公共方法时特别有用。
组合使用思路我总结过一套流程:先用 trace 找慢方法,再用 watch 看这个慢方法的参数和返回值,最后用 stack 确认调用来源。三步走下来,绝大多数方法级问题都能水落石出。
2.4 动态调整能力:ognl / redefine / retransform
当问题定位到具体代码,但临时修不了,Arthas 还提供了热调整手段。
ognl 命令能在运行时执行表达式。你可以读取对象字段、调用静态方法,甚至直接修改某个配置项。比如线上某个开关是 false,你不想发版想先改成 true 验证,ognl 可以做。它的操作方式有些像在 JVM 里塞了一个动态脚本控制台。这种能力很强大,但也必须警惕,改了之后要尽快恢复,并且记录在案。
redefine 和 retransform 用于热更新类的字节码。把编译好的新 .class 文件重新定义给 JVM 中已加载的类,进程不用重启,方法逻辑就变成了新版本。区别在于 retransform 的机制更安全,官方也更推荐优先使用 retransform。这类热更手段适用于紧急止血,比如线上有一行明显写错的判断条件,你先热更稳住局面,再走正常的构建发布流程。
这里有个极其关键的边界:redefine 只能改方法体,不能改变类的结构,也就是不能新增字段、不能增删方法。你要是改了结构,运行时大概率会报兼容性错误。还有一个坑一定要记住:热更修改的是 JVM 内存里的类,磁盘上的 jar 包完全没变,进程重启之后热更就丢了。所以用热更兜底之后,必须立刻推动正式发布,否则下次重启问题会原封不动地复活。
3. 从零到一:安装、连接与第一个命令
3.1 准备工作与启动方式
使用 Arthas 最推荐的方式是下载 arthas-boot.jar,单独放在一个工作目录,比如 /opt/arthas 下。执行java -jar arthas-boot.jar,它会自动扫描当前机器上的 Java 进程,列出所有带 PID 的候选目标,你输入编号回车就进入交互终端。如果已经知道目标进程的 PID,也可以直接java -jar arthas-boot.jar 12345一步到位。
第一次启动时,Arthas 会尝试把核心依赖下载到本地的 ~/.arthas 目录。能联网的环境它会自动完成,完全离线的服务器就要提前准备。我个人的建议是,在自己的电脑上先正常跑一次,把 ~/.arthas 目录打包,再传到目标服务器的同路径下,这样在现场就不会因为下载失败而抓瞎。
还有一个小细节:启动 Arthas 的 Java 版本最好和目标进程保持一致,或者比目标进程更高。JDK 9 以上的环境不需要特殊处理,Arthas 已经适配了模块化以后的 JDK 变化。版本如果差异过大,偶发会出现 attach 失败或者命令执行异常,虽然不算高频,但不值得在这种地方浪费时间。
3.2 attach 原理与容器环境常见坑
很多人第一次用 Arthas 觉得是魔法,其实背后的原理并不复杂。JDK 的 attach 机制允许外部进程以“附加”的方式连到目标 JVM,并加载 agent。Arthas 把 agent 加载进去之后,会在目标 JVM 内部启动一个独立的交互服务,你敲的每一条命令都是和这个服务通信,由它在目标进程内完成字节码增强、信息采集,再把结果返回给终端。
这套机制在普通服务器上通常很顺畅,但到了容器环境就经常出问题。最常见的报错是 “Unable to get pid of LinuxThreads manager thread”。这句话看起来像是线程问题,实际原因多半是权限不足。比如目标 JVM 是 root 启动的,而你用普通用户执行 Arthas;或者容器内没有访问 /proc 的权限。解决办法通常是切换到同一运行用户执行,或者在容器内给足 /proc 访问权限。使用 Kubernetes 时,一般 exec 进同一个容器再执行 Arthas 就能看到目标进程,不需要跨 Pod 操作。
还有一个容易踩的坑是:目标 Java 进程由某个脚本包装启动,实际 PID 不是一个标准 Java 进程,而是脚本进程。这种情况下需要先把脚本里的 Java 命令提取出来,找到真正的 Java PID,或者调整启动方式。遇到这种环境时,先确认目标进程的启动命令,再决定怎么 attach。
3.3 dashboard 实操输出解读
连接上之后,第一个命令我建议就跑dashboard。即使你还没有明确的问题方向,先看一眼全局状态也很有价值。
看面板的顺序我一般是这样:先扫一眼线程区,有没有大量 BLOCKED、WAITING 状态的线程。如果有一大片线程处于 BLOCKED,那很可能是锁竞争问题;如果大量线程 WAITING,可能是线程池队列堆积或者等待某个条件。紧接着看 CPU 占用比较高的几个线程,它们的名字通常是业务线程名的前缀,能帮你快速缩小范围。然后再看内存区和 GC 区,如果 old 区持续走高,Full GC 次数频繁,就把排查方向定到内存分配上。最后看一眼加载类数量,如果类数量异常大,就要怀疑动态代理或者反射生成类的代码。
dashboard 是持续刷新界面,记得用 q 退出。我开始用的时候一直按 Ctrl+C,结果发现 Ctrl+C 也能退,但有些版本会把它当作中断命令,导致终端残留一些输出。规范操作是直接 q,干净利落。
3.4 交互式命令行与批处理模式
进入 Arthas 之后,所有命令都支持 Tab 自动补全。你输入wa再按 Tab,它会把 watch 命令补全出来。每一条命令都可以加--help查看完整的参数说明,比如watch --help,这在记不清参数的时候非常救命。
除了交互模式,Arthas 还可以用管道方式批量执行命令。你可以写一个诊断脚本文件,比如 diagnose.txt,里面依次放 dashboard、thread -n 5、sc 某个类名、quit,然后执行:
java -jar arthas-boot.jar 12345 < diagnose.txt这种方式特别适合做标准化取证。当故障发生后,让执行脚本自动抓取固定信息,然后再轮到人工分析,既节省时间又保证不遗漏线索。Arthas 自己的日志默认记录在 ~/logs/arthas/arthas.log,里面包含启动信息、命令执行记录和结果。多人协作排查时,建议把日志目录统一收集一份,事后复盘就会非常清楚当时每个人敲了哪些命令、看到了什么结果。
4. 两个真实场景下的完整排查实录
4.1 场景一:CPU 飙高问题定位
一个下午,同事反馈说某个服务的单台机器 CPU 持续接近 100%,接口整体变慢,但没有任何异常日志。监控面板只能看到 load 很高、CPU 很高,完全不知道是哪个线程在消耗 CPU。
我登上那台机器,先跑了一条 dashboard。输出里线程区域显示有一个名为 async-process-7 的线程,CPU 占用率在九成以上,状态是 RUNNABLE。正常业务线程即使再忙,CPU 也很难满载;这种状态很有可能是死循环或者密集计算。
接着执行thread -n 3,这条命令会按 CPU 占用排序,打印出消耗最高的三个线程的完整堆栈。堆栈信息直接指向一个解析类的 parse 方法,而且从堆栈上看,这个线程一直在循环调用同一个方法,方法内部甚至还在循环处理。这就解释了 CPU 为什么降不下来。
然后我执行 watch 看方法入参,想确认到底是什么数据让它转不出来:
watch com.example.DataParser parse '{params[0]}' -n 5输出显示每次传入的都是同一个异常标记对象,再结合代码 review,发现某个状态字段的判断条件写反了,导致数据永远处于“需要重试”的状态。问题确认之后,改掉判断逻辑发布上线,CPU 立刻就降下来了。
整个过程从 attach 到确认根因,大约用了十几分钟。最让我感慨的是,全程没有重启服务,没有增加一行日志。以前这种问题,光加日志再等复现就要耗掉一整天。
4.2 场景二:接口耗时突然上升的链路拆解
另一个案例是下单接口的 p95 耗时从 200ms 涨到 1 秒,接口成功率还在 99.9%,所以一开始甚至没触发高级别告警。但用户体验已经明显变差,需要快速定位。
我登上出问题的机器,先用 trace 命令观察接口各段耗时分布:
trace com.example.OrderService createOrder '#cost > 500' -n 10输出是一棵调用树,createOrder 下面有三个子调用:validateUser 耗时 5ms,deductStock 耗时 120ms,sendMessage 耗时 800ms。答案几乎写在脸上了,问题出在消息发送这块。
继续深入,对 sendMessage 做 trace:
trace com.example.OrderService sendMessage '#cost > 300' -n 10结果发现 sendMessage 内部调用的 MQ 客户端 sendSync 方法占了 790ms。原来是消息发送使用了同步方式,而目标消息队列的主题正好在抖动,重试逻辑又加剧了耗时。再通过 watch 看发送的消息内容和重试次数,确认了消息体偏大导致占用较长。
最终方案是把同步发送改成异步发送,并增加了超时熔断。问题解决后,这个案例被我们内部反复提及:trace 的价值就是一次性把方法内部耗时分布摊开,不用靠猜,不用在代码里到处埋点打日志。
4.3 高频命令参数速记与实操心得
对于刚开始用 Arthas 的朋友,我整理了几个高频命令的固定写法,可以直接参考:
# 看某方法耗时超过 200ms 的调用链路 trace com.example.OrderService createOrder '#cost > 200' -n 10 # 观察方法入参和返回值,展开两层,最多 10 次 watch com.example.UserService getUserInfo '{params[0], returnObj}' -x 2 -n 10 # 只看方法抛异常的情况 watch com.example.UserService getUserInfo '{throwExp}' -e -n 5 # 打印方法的调用来源栈 stack com.example.UserService getUserInfo这里我要反复强调三个坑,都是我自己踩过的。
第一,观察类命令不加-n等于无限执行。我曾经在高频接口上敲 watch 忘记加次数限制,瞬间打出几万条输出,终端直接卡死,目标业务线程都被拖慢。现在我的习惯是:所有观察命令先带-n 1,看到结果没问题再放开次数。
第二,对象展开深度-x别一下设太大。从 1 开始,不够再加到 2。复杂对象在-x 4时会输出成百上千行,不仅看不完,还会把会话拖慢。需要精确看某个字段的话,可以用 OGNL 表达式精准取字段,比无脑深展开高效得多。
第三,类名必须写全限定名。曾经我写UserService而不是com.example.UserService,命令没有任何输出,我还以为工具坏了。后来养成了习惯:拿不准就先跑 sc 搜索,确认全限定名后再操作。
5. 进阶技巧与生产环境避坑指南
5.1 用条件表达式精准命中目标调用
生产环境的接口调用频繁,如果你贸然 watch 一个高频方法,哪怕加了-n 10,抓到的前 10 条也可能全是无关请求。这时候就需要用条件表达式过滤。
Arthas 支持用 OGNL 表达式作为过滤条件。比如只想看 userId 等于 9527 的调用:
watch com.example.UserService getUserInfo '{params, returnObj}' '#{params[0].userId == 9527}' -n 1注意最后面的#部分就是过滤条件,满足条件时才会输出。还可以用内置变量#cost做耗时过滤:
watch com.example.UserService getUserInfo '{params, returnObj}' '#cost > 1000' -n 10条件表达式相当于给你在高流量场景下装了一个精确的过滤器,大大减少无用信息。如果你不确定表达式的语法是否正确,可以先用 ognl 命令单独跑一次验证,再放进 watch 条件里。我见过不少同事因为表达式里一个引号没转义,导致命令执行失败,用 ognl 预跑一遍是最稳妥的方法。
5.2 批量脚本与异步取证
线上问题处理最怕现场混乱、操作不统一。我的习惯是维护一个 Arthas 诊断脚本库,针对不同故障类型准备固定脚本。
比如 CPU 飙高场景的脚本内容:
dashboard thread -n 5 thread --state BLOCKED quit接口变慢场景的脚本内容:
trace com.example.OrderService createOrder '#cost > 300' watch com.example.UserService getUserInfo '{params, returnObj}' -n 3 quit执行方式统一用管道传入:
java -jar arthas-boot.jar <pid> < diagnose_cpu.txt这样即使故障发生时你人在会议中,也可以让值班同事把标准化脚本跑一遍,把输出存下来。如果公司监控平台具备自动化操作能力,还可以把 attach 动作放进告警流程里,告警触发后自动抓取线程快照,等人回来直接看结果。
5.3 与监控、日志和链路追踪工具的配合
Arthas 不是孤岛,它和现有可观测性体系配合起来才能发挥最大价值。
监控平台负责发现哪台机器异常、哪个指标异常,Arthas 负责登录这台机器后做进程内部取证。大型分布式系统中,Arthas 单机看细节,链路追踪系统负责跨服务串联。一个典型的排查闭环是:先在监控面板看到支付服务某台机器 GC 次数飙升,再用 Arthas 连接这台机器,watch 那个内存增长最快的业务方法,发现某个大对象被反复缓存且没有释放;同时用链路追踪确认这些请求来自哪个上游服务,再去上游调整缓存尺寸。
还有一个实用技巧:Arthas 观察到的现象,要回填到日志平台验证。比如 watch 发现某个方法的某条参数异常,去日志平台搜这条参数的记录,看它的完整处理链路。这样能确认问题的影响范围,而不是仅仅看到单机单进程这一个片段。
5.4 生产环境的安全控制建议
生产环境使用 Arthas,安全和纪律比功能更重要。我总结了五条硬性要求:
第一,权限收敛。只有授权人员能登录服务器执行 Arthas,有条件的话通过统一的跳板机或者堡垒机操作,这样所有命令都有审计记录。第二,限制驻留时间。问题定位完就退出会话,不要让 Arthas 一直挂着。第三,观察命令必须带-n或者条件过滤,避免高频输出影响业务。第四,热更只用于紧急止血,用完必须走正规发版流程。第五,Arthas 的会话日志要统一收集归档,方便事后复盘和审计。
另外,不要在业务高峰期对几十台实例同时批量 attach。一台实例影响很小,但如果几十台同时被观察类命令增强,额外的 agent 线程和字节码增强会形成不可忽视的叠加开销。我之前见过一个事故复盘,就是因为故障处理时多人同时对多个节点开 trace,结果把本来就紧张的资源又压了几分,让恢复时间变长。
5.5 容器和平台环境的使用限制
在 Kubernetes 场景下,Arthas 能不能用,关键看权限。一般的做法是 exec 进业务容器,在容器内执行 Arthas,attach 同容器的 Java 进程。如果容器以非特权模式运行,或者安全策略禁止访问 /proc,attach 就会失败。
遇到这种限制,先别急着骂工具。优先联系平台团队确认是否能把 /proc 访问权限打开,或者调整容器安全上下文,加上 SYS_PTRACE 等能力。如果平台完全禁止这类操作,可以考虑把 Arthas 只用在测试环境和预发环境,线上问题依靠日志、追踪和 heap dump 来兜底。
还有一个冷门但重要的点:启动 Arthas 的进程和使用它的 shell 用户,应尽量和目标进程保持一致。跨用户 attach 在多数 Linux 发行版上会失败,除非你有足够的权限。所以规范操作是:目标程序用什么用户运行,就用什么用户执行 Arthas。
6. 常见问题速查表与踩坑实录
6.1 连接与 attach 阶段的问题
这应该是所有人遇到的第一个坎。我把高频问题整理成了速查表:
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 进程列表里找不到目标进程 | 使用用户不一致,或不在同一个 PID 命名空间 | 切换为同一运行用户执行,容器内需先进入对应容器 |
| 报错 Unable to get pid of LinuxThreads manager thread | /proc 不可访问或权限不足 | 放开 /proc 权限,容器中调整安全上下文 |
| attach 后目标进程响应明显变慢 | 老版本 JDK 兼容问题或目标进程正在 Full GC | 升级 Arthas、对齐 JDK 版本,等待 GC 结束后再试 |
| 启动 Arthas 时下载依赖失败 | 服务器无法访问公网 | 提前在本地打好 ~/.arthas 目录,离线导入 |
这类问题有个共性:九成是权限问题,不是工具问题。遇到 attach 失败,先检查用户、再检查 /proc、最后检查容器限制,按这个顺序排查,基本都能解决。
6.2 命令执行后的输出异常
watch / trace 执行后没有输出,是最让人困惑的情况。绝大多数原因是方法签名不对,或者条件过滤写得太严,或者目标方法根本没有新调用。我的排查顺序是:先用 sc 确认类加载,再用 sm 确认方法签名,然后去掉过滤条件,最后才考虑增强是否生效。
输出刷屏也很常见。解决办法前面提过,强制加-n 1。如果不小心刷屏了,按 Ctrl+C 终止当前命令,但注意有些版本 Ctrl+C 会同时退出会话,这时候需要重新 attach。
还有一个关于 dashboard 的小坑:在较新版本中 dashboard 面板用 q 退出,但在某些终端模拟器下按 q 可能没反应。这时候按两次 q 或 Ctrl+C 都能退出,不用慌。
6.3 热更新不生效的排查
redefine 和 retransform 指令执行成功,但代码行为没变,是热更场景里最容易翻车的问题。常见原因有三个:第一个是新的 class 文件在编译时的包名和类名与目标不一致;第二个是你修改的不仅是方法体,还包括字段或方法数量,这超出了 redefine 的能力边界;第三个是目标类被多个 classloader 加载,你只更新了其中一个加载器的版本。
热更之后进程重启,代码恢复为 jar 包里的旧逻辑,这也是很多人忽略的。我见过一个真实案例:同事用热更把线上一个逻辑临时修复了,第二天发布新版本时他忘了热更这回事,新包内容本身没问题,但为了保险起见他还特意测试了那个逻辑。结果发布后功能反而“失效”了,一查才发现旧逻辑已经被正确的新代码覆盖了,热更的临时补丁自动消失,属于虚惊一场。这说明热更只适合救火,救完火必须走正式流程,并且要在变更记录里写清楚。
6.4 我自己的几条独家建议
针对 Arthas 的使用,我整理了几条很少在文档里看到的经验。
第一,新环境第一次使用不要等到事故时再试。找一台测试机,起一个 Spring Boot 应用,把 dashboard、thread、trace、watch 全部跑一遍,先熟悉真实的输出格式。真出事的时候你没时间研究命令参数。
第二,写命令时永远带包名,永远带-n。这是两条铁律,能帮你躲开大部分坑。
第三,排查过程中每一步的关键输出都要保存。我通常会在终端里把输出重定向到文件,或者打开 Arthas 日志留痕。等到复盘时、写报告时,这些第一手证据远比记忆可靠。
第四,善用ognl作为逻辑探针。有时你不想对某个方法做增强,只想看一个静态配置目前是什么值,ognl 直接读取即可,比 watch 轻量得多。比如想查看某个全局单例里缓存了哪些 key,一条 ognl 表达式就能拿到。
这篇文章写到这里,基本把我实际使用 Arthas 的心得都倒出来了。这个工具真正改变了 Java 线上问题的处理方式,让我从频繁改代码发布试错,变成了登录机器直接观察求证。说实话,用过一阵子之后,你已经很难回到过去那种靠猜和靠运气的排查方式了。后面你如果遇到具体问题,多翻一翻命令帮助,多在测试环境试几遍,很快就能把它变成自己工具箱里最顺手的一件。