简介:Arthas 3.7.2 是一款开源 Java 诊断工具的生产级资源包,面向需要在线定位问题、分析性能瓶颈的 Java 后端开发者,也适合用于毕业设计论文中的运行时行为研究、计算机案例解析及系统软件二次开发。包内收录完整源码、官方文档与辅助脚本,涵盖命令行交互、类加载器查看、JVM 实时监控、SQL 执行跟踪、热修复等核心模块,可帮助读者对照实现细节理解工具设计思路。资源包共 2000 个文件,以 Java 源码、Markdown 文档、PNG 截图、JSON 配置为主,并包含 Vue 前端、Shell 启动脚本、Dockerfile 等扩展内容;压缩后仅 10.76MB,目录结构清晰,便于检索与归档。目前已有 127 人学习浏览,适合中高级 Java 工程师按需查阅。通过学习这份资源,开发者可掌握如何用 arthas-boot 附加到目标 Java 进程,熟悉 watch、jvm、classloader、sql 等命令的实战用法,并参考内置的 IDE 插件、Web 控制台及代码覆盖率方案,快速建立自己的线上诊断工作流,显著提升排错与调优效率。
1. Arthas 是什么:Java 线上问题诊断不重启的第一选择
线上服务告警,接口慢得像爬,日志里全是超时异常。你在服务器上翻半天,想加一行日志看看入参和返回值,结果发现改完代码还得重新打包、发布、重启,一套下来十几分钟没了。这段时间用户可不会等你。这种情况我经历过太多次,后来几乎都是用 Arthas 救场。Arthas 是阿里巴巴开源的 Java 诊断工具,v3.7.2 这个版本对启动速度、命令交互和中文输出做了不少优化,是当前相当稳定的一个版本。它的核心价值一句话能说清:不重启 Java 应用,就能实时查看方法入参、返回值、异常、线程栈、JVM 内存,甚至能直接热更新类的行为。适合后端开发、线上运维、做毕业设计需要研究 JVM 内部机制的同学。它解决的痛点是线上问题诊断的"黑匣子"问题——程序在跑,你看不到里面发生了什么,而这个工具把黑匣子掀开了。
2. 核心命令实战:从 attach 到 trace 的完整诊断链路
2.1 启动与附加:arthas-boot 的两种常见用法
拿到资源包后,你会看到arthas-boot.jar以及配套的as.bat、as-service.bat脚本。在 Linux 服务器上,最常见的启动方式就是直接跑 boot jar:
java -jar arthas-boot.jar执行之后它会扫描当前机器上的所有 Java 进程,打印成一个列表让你输入序号。回车之后就 attach 上了,出现[arthas@PID]$的提示符,说明已经进入交互状态。
如果目标进程确定,更推荐直接指定 PID 省掉手动选择一步:
java -jar arthas-boot.jar 26742这里 PID 是目标 Java 进程的进程号。可以用ps -ef | grep java先查出来,或者用jps -l看 Java 进程与其主类名。在容器环境里需要注意:Arthas 需要和目标进程在同一 PID namespace 内,所以要在容器内部执行,或者用--target-ip和--telnet-port参数远程连接。Windows 上则直接双击as.bat或者在命令行执行,效果和 Linux 一致。
提示:第一次启动 boot 会自动下载依赖的 asm、commons-lang 等第三方库,机器如果没有外网权限会失败,此时需要把这个资源包里的 lib 目录完整放到 arthas 安装目录。
进入交互界面后,第一件事不是急着查问题,而是确认自己连对了进程。执行pid命令能看当前 attach 的 PID,执行version确认 Arthas 版本。这一步非常重要,连错进程是新手最常犯的错误,尤其在服务器上同时跑着多个 Java 服务的时候。
2.2 基础三板斧:dashboard、thread、jvm
attach 成功后的第一个标准动作是dashboard。这个命令会实时刷新 CPU、内存、GC 和线程概览,是我每次排障的起点:
dashboard输出里重点看三块:CPU 占用率最高的线程 ID、GC 次数和耗时、堆内存各区使用率。如果看到 old 区持续逼近上限且 GC 频繁,基本可以断定是内存泄漏或者大对象分配问题;如果某个线程 CPU 持续打满,就需要结合线程栈进一步定位。
thread命令则直接解决"哪个线程在搞事"的问题。最常用的是thread -n 3查看 CPU 占用最高的 3 个线程:
thread -n 3输出会给出线程名、ID、CPU 时间占比,以及完整的线程栈。我一般会把栈里的包名和方法名和业务代码做映射,判断是业务死循环、GC 线程竞争还是锁等待。还可以用thread -b直接找出死锁线程组,这个参数在做锁排查时非常省事,后面进阶章节会再讲。
jvm命令则输出完整的 JVM 运行信息,包括堆/非堆内存、垃圾回收器类型、JIT 编译时长、类加载数量等。它的价值在于长期监控——我会定期jvm并对比不同时间点的数据,观察内存增长趋势,比单次看一个点的数据靠谱得多。
这里要说明白:三板斧的定位是快——30 秒内建立对进程整体健康度的判断。它们回答"哪里有问题"而不是"为什么有问题"。要深入"为什么",就得用到方法级监控。
2.3 watch 与 trace:方法级动态观测
watch是 Arthas 里含金量最高的命令,它能在不修改代码的前提下,动态观测一个方法的入参、返回值和异常。比如排查支付接口时,我常这么写:
watch com.example.PayService doPay '{params, returnObj, throwExp}' -x 3命令拆开看:com.example.PayService是类名,doPay是方法名,'{params, returnObj, throwExp}'是一个 OGNL 表达式,指定你要看哪些数据。-x 3表示对结果做 3 层深度展开,防止嵌套对象太多导致刷屏。
执行后,每次调用 doPay 都会打出一行 JSON 格式的观测结果,包括入参、返回值和异常堆栈。实战中最惊艳的场景是:日志里明明有异常但信息不完整,用watch直接看throwExp就能拿到完整栈,连日志都不用改。
trace命令则负责方法内部调用链路。它打出的不是单次调用的数据,而是一个方法内部所有子调用的耗时分布:
trace com.example.PayService doPay输出会显示 doPay 内部每个子方法被调用的次数和总耗时,一眼就能看出"慢在哪个子调用上"——可能是远程 HTTP 调用、数据库查询,或者某个本地算法。有了这个信息,优化方向立刻明确,不用像无头苍蝇一样逐段查代码。
2.4 条件表达式与结果输出
线上使用 watch 最大的风险是性能损失。假设一个订单接口每秒调用上千次,你直接 watch 它,等于给每次调用都加了额外的拦截逻辑,接口 RT 可能直接翻倍。解决方法是加条件表达式过滤:
watch com.example.PayService doPay '{params[0].orderId, returnObj}' 'params[0].amount > 100'最后一个参数就是过滤条件,表示只观察订单金额大于 100 的调用。它的底层是 OGNL 表达式求值,除了基本比较,还能写更复杂的逻辑,比如组合条件:
watch java.lang.String toString '{params[0]}' 'params.length > 0 && params[0] != null'实际排障时我更常配合#cost变量使用,它代表方法执行耗时(毫秒数)。只观察耗时超过 200ms 的慢调用:
watch com.example.PayService doPay '{params, returnObj}' '#cost>200'这一招在压测和线上慢请求排查中极其实用,能把诊断引入的额外开销降到最低。
3. 热修复与类加载器排查:不重启应用的改动方案
3.1 热修复原理:从 java.lang.instrument 到 retransformClasses
Arthas 的热修复能力底层依赖 Java Agent 技术。启动时通过java -javaagent或者 attach 机制把 agent 挂载到目标 JVM,拿到Instrumentation接口的实例,进而调用retransformClasses在运行时替换类的方法体。这和 AOP 的代理逻辑有本质区别:AOP 是给对象创建代理类,而 retransformClasses 是直接修改已经加载的字节码,连静态方法都能换。
实际使用中,这套机制配合 Arthas 的命令行入口,形成了一个完整的闭环:先用jad反编译出目标类的当前字节码,修改后mc内存编译成 class 文件,最后redefine加载进去。整个过程不重启、不打断服务。
但热修复不是万能的。有一个边界要认清:redefine 只能修改方法体,不能增删字段或方法签名,更不能改变类的继承结构。因为 JVM 的类解析机制在类加载阶段就确定了字段和方法布局,运行时改动这些结构会直接抛出UnsupportedOperationException。所以热修复适合的是——修一个判断条件、加一行日志、改一个返回值的 bug 修复场景。
3.2 jad 反编译:看到线上类真实的样子
有一次线上出现一个诡异问题,代码评审里明明判断了空值,线上还是抛了 NPE。后来用jad一看,发现部署的 jar 是旧版本,代码里根本没有空值判断。所以我的第一个建议是:不要相信你本地代码,以线上 jad 输出为准。
jad --source-only com.example.OrderService > /tmp/OrderService.java--source-only只输出源码不附带反编译信息,重定向到文件方便修改。如果不带这个参数,默认还会打印 Arthas 反编译的版本信息和类加载器哈希,这些在视觉上有干扰。
反编译出来之后,直接在源码里修改。这里有个常用技巧:只改动需要修的那几行,别做大篇幅重构,因为改动越大,redefine 失败后回滚定位就越麻烦。
3.3 mc + redefine:动态更新类行为
拿到修改后的 Java 文件,下一步是编译和注入。先查目标类所属的类加载器哈希值:
classloader输出会列出当前 JVM 里所有类加载器的哈希值、名称和父子关系。找到加载com.example.OrderService的那个加载器,比如是psr的 WebappClassLoader,记下哈希值。
然后执行mc做内存编译:
mc -c 21d0b3b6 -d /tmp /tmp/OrderService.java参数含义:-c指定类加载器哈希,-d指定编译输出目录。编译成功会提示生成/tmp/com/example/OrderService.class,最后注入:
redefine /tmp/com/example/OrderService.class执行后没有任何报错,改动即生效。此时再调用服务,行为已经是新逻辑了。整个过程不超过两分钟,对比改代码重新发布的十几分钟,优势非常明显。
注意:redefine 对方法体内的修改通常有效,但如果类里引用了不存在的类或常量,会在编译或 redefine 阶段直接失败。所以生产环境做热修复前,先用
jad完整查看类依赖。
3.4 classloader 命令:排查类加载异常
类加载问题是 Java 世界里最容易让人抓狂的问题之一,症状千奇百怪,报错却很简单——NoClassDefFoundError或者ClassNotFoundException。Arthas 的classloader命令加-t参数可以打出完整的类加载树:
classloader -t这个树能直接展示 JVM 里所有类加载器的层级结构,包括 Bootstrap、Ext、App 以及各种容器加载器。类加载冲突的本质是:同一个类被多个加载器各加载了一份,导致ClassCastException或者instanceof判断失败。
更精确定位某个类由谁加载,用sc命令:
sc -d com.example.OrderService输出里会标注ClassLoader字段,显示该类实际由哪个加载器负责。对比classloader -t的输出,就能确认是否出现了"父子加载器各加载一份"的冲突。
我曾经排查过一个典型的 Spring Boot 应用问题——引入两个版本的 JSON 库,一个由 AppClassLoader 加载,另一个被 Tomcat 的 WebappClassLoader 加载,序列化时直接 ClassCastException。整个过程用 Arthas 不到 5 分钟就定位了,如果靠猜,可能要折腾一下午。
4. 常见问题与避坑:线上诊断的六个翻车现场
4.1 attach 失败或找不到目标进程
现象:执行 arthas-boot 后提示Can not find java process,或者选择了 PID 后长时间没有响应。
原因:最常见的有三种。一是目标进程不是 Java 进程,boot 扫描时只显示 Java 进程,你选错了自然失败;二是 JDK 版本过老,Arthas 3.x 官方要求 JDK 8+,JDK 6/7 会有兼容问题;三是容器和宿主机 PID namespace 不一致,你在宿主机看到的 PID 在容器里不存在。
解决:先用ps -ef | grep java确认进程存在;容器环境在容器内部执行 Arthas;JDK 版本问题直接换 JDK 8+ 或者用低版本 Arthas。曾经我就是在 Kubernetes 里折腾了半天 attach 不上,最后发现 Pod 里根本没有这个 PID,因为看到的 PID 是宿主机视角的。
4.2 高频接口 watch 导致性能劣化
现象:watch 一个热点方法后,接口 RT 明显上升,甚至触发新的告警。
原因:watch 默认对所有调用生效,等于在每次方法调用时都做一次 OGNL 表达式求值和结果格式化。对高频方法,这个额外开销会被放大。
解决:永远使用条件表达式缩小范围,只观测慢请求或特定参数。我最常用的就是'#cost>200'过滤,只观测超过 200ms 的调用。实在需要全量观测,挑选低峰期操作,避免在业务高峰期做这种诊断动作。
4.3 redefine 后想回滚却不生效
现象:改了类但改错了,想 redefine 成原始的 class 文件,结果报错或者行为没变。
原因:redefine 机制只允许类的版本"前进"不允许"后退",也就是当你 redefine 了类 A,就不能再用旧版本的 A.class 重新 redefine。
解决:在操作前先保存原始 class 文件,用dump命令导出初始字节码。但即便有原始文件,redefine 通常也会被拒绝。正确的做法是把改动前的逻辑保留在代码库里,redefine 到预期版本,最终还是要靠重新发布恢复。这是一条血泪经验:生产环境做热修复前,先确认重启流程是通畅的,不要把 redefine 当成唯一的救命稻草。
4.4 中文输出乱码和表格错位
现象:dashboard 输出里中文全是问号,或者表格线对不齐,看得人头皮发麻。
原因:Arthas 的输出依赖终端字符集和列宽,终端宽度不够时表格会自动换行导致错乱。
解决:在 Linux 上先执行export LANG=zh_CN.UTF-8再启动 Arthas;Windows 上确保控制台代码页是 65001。如果输出内容实在太长,可以把结果重定向到文本文件再查看:
watch com.example.PayService doPay '{params, returnObj}' -x 3 > /tmp/watch.log4.5 低版本 JDK 下的 ClassFormatError
现象:attach 成功但执行命令时报ClassFormatError或者UnsupportedClassVersionError。
原因:Arthas 自身的字节码版本和目标 JVM 的类加载机制不兼容,常见于 JDK 6/7 老应用。
解决:换用 Arthas 3.1.x 或更早版本,老版本对旧 JDK 兼容性更好。团队如果是统一标准 JDK 8+,直接用 3.7.2 没有问题。
4.6 诊断完没有退出导致进程存活
现象:用完 Arthas 后直接关掉终端,但 Java 进程里还有 arthas agent 在跑,占用了额外的内存和线程。
原因:attach 后 agent 会常驻在目标 JVM 里,直接断开终端并不会自动卸载。
解决:用完执行stop命令优雅退出,它会卸载 agent 并释放资源。这是很多人都会忽略的细节,我早期就吃过这个亏,一个应用被挂了十几个 Arthas agent,白白耗着内存。
5. 进阶技巧:火焰图生成与线上诊断的标准动作
5.1 profiler 命令生成 CPU 火焰图
当三板斧和 watch/trace 都不足以定位性能瓶颈时,就该上火焰图了。Arthas 内置了 profiler 功能,底层用的是 async-profiler,采样开销小到可以忽略。
profiler start执行后开始采样,让它跑 1 到 2 分钟,覆盖一个业务高峰期,然后停止并生成火焰图:
profiler stop --format html -d /tmp/flamegraph.html生成的是 HTML 格式火焰图,直接用浏览器打开。火焰图怎么看?越宽的栈帧代表在采样期间占用的 CPU 时间越多,找最宽的"平顶"逐层展开,就能定位到真正的热代码路径。我印象最深的一次排查:一个服务 CPU 持续打满,用 thread 只看到 GC 线程繁忙,火焰图一生成才发现是正则表达式回溯导致频繁触发 Young GC。这个结论靠猜是绝对猜不出来的。
Arthas 的--format参数除了支持 html,还支持jfr(Java Flight Recorder)格式,可以直接导入 JMC 分析。
5.2 锁等待与死锁的快速定位
thread -b是排查死锁的利器,直接输出当前 JVM 里处于死锁状态的线程以及它们的锁依赖关系:
thread -b如果服务出现大面积请求阻塞,执行这个命令能立刻看到哪些线程互相持有锁不释放。比起jstack一次打印所有线程栈然后人工翻找,thread -b精准得多。我一般会在thread -n 3发现线程 BLOCKED 状态异常时,紧接着补一个thread -b确认是不是死锁。
5.3 诊断习惯:把 Arthas 做成标准动作而不是最后的救命稻草
用了几年 Arthas 之后,我最大的收获不是会敲多少命令,而是总结出了一套固定的排障流程。
遇到线上问题,我现在不慌着看日志,而是先dashboard看整体负载和 GC,再thread -n 5看线程热点,接着用watch或trace定位具体方法,实在不行上火焰图。整个过程像条件反射一样自然。这套流程在团队里也被我固化成文档,新同学上手排障时按步骤走,基本都能在 5 分钟内给出初步结论。
还有两个习惯值得一提。第一,所有诊断命令尽量加条件过滤,减少对线上业务的影响;第二,诊断完一定记得stop,不让 agent 常驻。从那以后,我每次 attach 之前都会强制走一遍"先 dashboard 再 thread 再定位"的流程,这让我在排障时很少再做无用功。希望这份笔记对你也有帮助,遇到线上 Java 问题,别再靠重启解决问题了。
本文还有配套的精品资源,点击获取