拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Arthas命令详解:不重启诊断Java线上问题与性能瓶颈

Arthas命令详解:不重启诊断Java线上问题与性能瓶颈 简介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 jarjava -jar arthas-boot.jar执行之后它会扫描当前机器上的所有 Java 进程打印成一个列表让你输入序号。回车之后就 attach 上了出现[arthasPID]$的提示符说明已经进入交互状态。如果目标进程确定更推荐直接指定 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、jvmattach 成功后的第一个标准动作是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} #cost200这一招在压测和线上慢请求排查中极其实用能把诊断引入的额外开销降到最低。3. 热修复与类加载器排查不重启应用的改动方案3.1 热修复原理从 java.lang.instrument 到 retransformClassesArthas 的热修复能力底层依赖 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 8JDK 6/7 会有兼容问题三是容器和宿主机 PID namespace 不一致你在宿主机看到的 PID 在容器里不存在。解决先用ps -ef | grep java确认进程存在容器环境在容器内部执行 ArthasJDK 版本问题直接换 JDK 8 或者用低版本 Arthas。曾经我就是在 Kubernetes 里折腾了半天 attach 不上最后发现 Pod 里根本没有这个 PID因为看到的 PID 是宿主机视角的。4.2 高频接口 watch 导致性能劣化现象watch 一个热点方法后接口 RT 明显上升甚至触发新的告警。原因watch 默认对所有调用生效等于在每次方法调用时都做一次 OGNL 表达式求值和结果格式化。对高频方法这个额外开销会被放大。解决永远使用条件表达式缩小范围只观测慢请求或特定参数。我最常用的就是#cost200过滤只观测超过 200ms 的调用。实在需要全量观测挑选低峰期操作避免在业务高峰期做这种诊断动作。4.3 redefine 后想回滚却不生效现象改了类但改错了想 redefine 成原始的 class 文件结果报错或者行为没变。原因redefine 机制只允许类的版本前进不允许后退也就是当你 redefine 了类 A就不能再用旧版本的 A.class 重新 redefine。解决在操作前先保存原始 class 文件用dump命令导出初始字节码。但即便有原始文件redefine 通常也会被拒绝。正确的做法是把改动前的逻辑保留在代码库里redefine 到预期版本最终还是要靠重新发布恢复。这是一条血泪经验生产环境做热修复前先确认重启流程是通畅的不要把 redefine 当成唯一的救命稻草。4.4 中文输出乱码和表格错位现象dashboard 输出里中文全是问号或者表格线对不齐看得人头皮发麻。原因Arthas 的输出依赖终端字符集和列宽终端宽度不够时表格会自动换行导致错乱。解决在 Linux 上先执行export LANGzh_CN.UTF-8再启动 ArthasWindows 上确保控制台代码页是 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还支持jfrJava 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 问题别再靠重启解决问题了。本文还有配套的精品资源点击获取
返回列表