Arthas进阶实战:线上JVM问题排查与命令详解
2026/9/9 18:55:38 网站建设 项目流程

中午刚处理完一个线上接口超时的问题,用Arthas定位到是某个第三方调用在等待锁,全程没重启服务、没加一行日志,十分钟内从怀疑到确认。如果你还在靠反复加日志、重启复现来排查线上问题,那我强烈建议你认真看看这篇文章。

Arthas是阿里巴巴开源的Java诊断工具,能在不修改代码、不重启应用的前提下,对运行中的Java进程做实时排查和动态干预。它解决的正是Java开发最头疼的线上问题:无法本地复现、日志不完整、临时加日志要重新发版。这篇文章适合已经被Arthas基础命令打动过、但还想用得更加深入的人,也适合那些装好了Arthas却因为启动就报错而搁置的人。我会把启动疑难、URL调用路径追踪、命令的底层原理和实战场景串起来,一次性讲透进阶用法。

2. 为什么线上问题必须用Arthas这类工具解决

2.1 线上诊断的“最后三分钟”困境

说句实在话,大部分Java应用的问题,不是查不到,而是来不及查。一个接口偶发超时,本地压测压不出来;一个内存缓慢增长,日志里除了OOM前的一点痕迹什么都看不到;一个死锁,现场一重启就没了。传统的排查手段是:怀疑哪个方法有问题,就在哪个方法里加日志,重新打包、发布、等待问题复现、收集日志、分析、再改。这一套循环下来,轻则几小时,重则几天,而且很多线上故障根本等不起这个周期。

Arthas的价值在于,它把“诊断”这个动作从开发态搬到了运行态。你不需要预埋任何探针,不需要提前在代码里写日志,而是等问题发生的那一刻,像外科手术一样精准地伸进JVM内部,看线程栈、看方法入参返回值、看调用耗时、甚至直接修改字节码。这等于给线上系统装了一个随时可用的显微镜。

2.2 Arthas和JDK自带工具的定位差异

JDK自带的那套工具,jstack、jmap、jstat、jcmd,解决的是“系统级指标”问题:线程数多少、堆占用多少、GC频率如何。但它们的短板同样明显:你能看到线程状态,却看不到这个线程正在执行的业务方法参数;你能看到堆内存占用,却不知道是哪段代码创建的这些对象。

Arthas的能力恰恰是“代码级”的。它基于Java的Instrumentation机制,在类加载之后动态织入增强逻辑,让你能在任意方法上做“临时埋点”。这种能力在原生JDK工具里是没有直接对应的。举一个直观的例子:jstack能告诉你线程Blocked在某个锁上,但不会告诉你这个锁是被谁持有的、持有了多久、业务参数是什么。而Arthas的thread命令可以显示锁对象信息,配合watch命令盯住锁资源,整个问题链条就完整了。

所以我的建议是:jstack/jmap这类工具用于“粗筛”,Arthas用于“精确定位”。两者配合,效率最高。如果你还在用笨办法加日志排查,强烈建议把Arthas纳入你的常备工具箱,它解决的是“最后三分钟”的临门一脚问题。

3. 核心命令的底层逻辑与实战拆解

3.1 命令不是背出来的:先理解Arthas的命令模型

接触过Arthas的同学应该知道,它启动后进入一个交互式命令行,输入命令回车执行。很多人觉得命令难记,其实是不理解Arthas的命令模型。所有的Arthas命令,本质上做两件事:要么是查询JVM运行信息,要么是在类的方法上做字节码增强。

增强类命令(watch、trace、tt、monitor、stack)有统一的参数模式:

命令名 类名 方法名 [条件表达式] [监听选项]

类名和方法名用来定位“在哪里增强”,条件表达式用来过滤“什么时候触发”,监听选项控制“增强后做什么”。理解了这个模型,你遇到一个没见过的命令,也能大概推断出它需要哪些参数。

举个例子,watch com.example.OrderService createOrder '{params, throwExp}' -x 2这条命令,拆开看就是:在OrderService类的createOrder方法上做监听,当方法被调用时,打印入参和异常信息。这是Arthas最核心的思维模式。

3.2 高频命令的细节用法与适用场景

dashboard是入门命令,显示整体大盘:线程、内存、GC、运行时信息。它的进阶用法是可以加-i参数调整刷新间隔(毫秒)。排查内存问题时,我会把dashboard开着,同时触发业务操作,观察堆内存曲线的变化节奏,判断是否存在“每次都涨但不回落”的泄漏特征。

thread命令是排查线程问题的利器。thread -n 3直接列出CPU占用最高的三个线程,会带上线程栈。这是排查CPU飙高的首选命令。有个细节很多人不知道:thread -b可以找出当前阻塞其他线程的锁,也就是死锁问题的“罪魁祸首”。配合thread --state BLOCKED可以只看阻塞态的线程。

watch是日常用得最多的命令,它能在方法执行前后取到入参、返回值、异常。关键选项有三个:-x控制展开深度,-b在方法调用前执行(此时拿不到返回值),-e在异常抛出时触发。实战中我常用watch 类名 方法名 '{params, returnObj, throwExp}' -x 3来一次性拿到完整信息。

trace命令是性能瓶颈分析神器。它能输出方法内部各子调用的耗时分布,精确到每个被调方法。值得注意的是,trace默认只追踪一层调用关系,想看更深的调用链需要通过-n或多次trace嵌套来完成,或者使用--skipJDKMethod false把JDK内部方法也纳入统计。排查慢接口时,trace的输出能直接告诉你时间花在哪个子调用上。

tt命令是“时间隧道”,它能记录方法调用现场,之后可以回放。对于那种“偶发异常、过了就没了”的问题,tt是救命级的工具。tt -t 类名 方法名开始记录,tt -i 索引查看某次调用的完整参数和返回值,tt -p 索引甚至可以重放一次调用。

3.3 ognl表达式:Arthas里最被低估的能力

Arthas的命令参数大量使用ognl表达式,它就是在运行时对对象图做导航和计算的表达式语言。你可以把它理解为“在JVM里执行的、可以访问到当前实例和上下文的Java代码片段”。

比如你想查看一个Spring Bean的某个属性:

ognl -x 3 '#bean=@com.example.ApplicationContextUtil@getBean("userService"), #bean.userCache.size()'

这条命令调用了静态方法获取Bean,然后读取了它内部的一个缓存对象的大小。这种能力在排查“缓存什么时候被清空了”“配置到底生效没有”之类的问题时极其实用。

ognl表达式的学习门槛在于熟悉上下文变量。在watch命令里,paramsreturnObjthrowExptarget是固定的四个上下文对象;在ognl命令里,你可以用@类名@静态方法调用静态方法,用#变量名=表达式, 表达式做多步操作。花一两个小时把ognl练熟,Arthas的战斗力会提升一大截。

4. 启动疑难实录:Arthas启动时拿不到jps进程怎么办

4.1 为什么Arthas找不到目标Java进程

很多人在第一次接触Arthas时,卡住的不是命令不会用,而是压根启动不了。最常见的一种报错场景是:执行java -jar arthas-boot.jar之后,Arthas列出进程列表,但你想诊断的那个Java进程根本不在列表里,或者提示“Can not find java process”。

这里要先明白Arthas是如何发现进程的。Arthas通过JDK的jps工具(Java Virtual Machine Process Status Tool)来枚举本机Java进程。jps的原理是扫描临时目录下的hsperfdata_<用户名>目录,每个运行中的JVM会在这个目录下生成一个以PID命名的文件。如果这个文件不存在、读不到、或者目录位置不对,jps就发现不了对应的进程。

所以Arthas找不到进程,绝大多数情况是jps本身就看不到那个进程。理解了这一层,排查思路就清晰了。

4.2 jps失效的几类典型场景

**场景一:进程归属用户不一致。**如果你用root用户执行Arthas,但Java进程是tomcat用户启动的,那么Arthas去扫描root用户目录下的hsperfdata,自然找不到tomcat用户启动的进程。解决方案是切到与目标进程相同的用户来执行Arthas。

场景二:临时目录被清理。/tmp目录下的hsperfdata文件可能被系统的定时清理任务删除,或者被手动清理。JVM虽然还活着,但它那个性能数据文件已经没了,jps自然就看不到它。这种时候,重启应用是最后的办法,但在重启之前,还有更轻量的方案可以抢救一下。

**场景三:容器环境隔离问题。**在Docker或K8s里,Arthas跑在宿主机上,目标Java进程跑在容器里,两边看到的PID命名空间不同,临时目录也隔离,jps基本不可能跨容器发现进程。

**场景四:JVM参数显式关闭了PerfData。**如果启动参数里有-XX:-UsePerfData,JVM不会生成hsperfdata文件,jps永远发现不了这个进程。

4.3 绕过jps:手工指定进程的三种方法

既然jps不可靠,那就有必要绕开它。Arthas提供了--pid参数,可以直接指定目标进程PID,绕开进程枚举环节:

java -jar arthas-boot.jar --pid 12345

这个方式的前提是你知道目标进程的PID。如何获取?在容器场景里,PID不是关键问题,关键是要在同一个PID命名空间下执行。所以最稳妥的方式是把arthas-boot.jar复制到容器内,在容器内部执行。如果是K8s环境,可以用kubectl exec进入容器再操作。

还有第二种方式,利用Java的Attach机制主动连接。Arthas支持通过attach参数直接连接,思路是让Arthas自己去找JVM并attach上去,不需要jps先枚举出来。你可以在启动Arthas之前,先用ps -ef | grep java找到完整启动命令,确认进程确实存在且健康状态,再用--pid方式连接。

第三种方式,是修改Arthas查找进程的策略。如果你用的是较新版本的Arthas,可以考虑设置环境变量ARTHAS_IGNORE_JPS=1,让Arthas改用其他方式枚举进程。这个选项在不同版本里支持程度不同,建议先查一下对应版本的文档。如果这些方式都走不通,唯一剩下的办法是重启应用,并在重启后第一时间用--pid方式连接。这个顺序一定要记清楚:先确认进程存在,再尝试各种连接方式,最后才考虑重启。

4.4 实战教训:容器内启动Arthas的避坑清单

容器环境里踩过的坑最多,我总结了几条经验:

  • **必须使用与目标进程相同的用户执行Arthas。**很多容器镜像以非root用户运行应用,如果Arthas以root启动,attach时可能因为权限问题失败。
  • **确保容器内存在java命令。**Arthas运行阶段需要java环境,有些精简镜像只有JRE,没有JDK。虽然Arthas附带了大部分需要的类,但缺少关键工具时还是会报错。解决方法是使用带JDK的镜像,或者把arthas-boot.jar放在宿主机、用--target-ip方式远程attach。
  • **小心PID命名空间。**在容器内执行ps -ef看到的PID,和宿主机看到的不一样。在容器内使用--pid时必须使用容器内的PID,而不是宿主机的PID。
  • **内存受限的容器注意Arthas自身开销。**Arthas启动时会加载不少类,占用一些内存。在内存吃得非常紧的容器里,建议把Arthas的堆内存限制下来,可以通过JAVA_OPTS传入-Xmx256m之类的参数,避免因为Arthas启动导致容器OOM。

如果一个进程连jps都发现不了,但它明明还活着,先用ls -l /tmp/hsperfdata_<用户>/确认一下目录内容,如果文件不在,那么任何来自jps枚举的方式都是白费功夫。直接跳到--pid方式,这是最可靠的路径。

5. URL调用路径追踪实战:能不能跟、怎么跟

5.1 trace命令跟踪HTTP请求的先天局限

有一个高频问题:Arthas能不能直接跟踪一个URL的完整调用路径?很多人的预期是,我输入一个URL,Arthas就自动显示出这个请求从Controller到Service到DAO的全链路耗时。但Arthas本身是不支持“URL维度的自动链路追踪”的。

原因很简单:URL只是HTTP协议层面的概念,进入JVM之后,它变成了一次方法调用链。Arthas做的是代码层面的字节码增强,它不知道也不关心“这次调用是由哪个URL发起的”。它只知道“这个方法被调用了,参数是什么,耗时多少”。这和APM工具(如SkyWalking、Pinpoint)的工作机制有本质区别,后者会通过字节码增强自动维护一个TraceId,把所有跨方法、跨线程的调用串联起来,形成一条完整的调用链视图。

Arthas的思路不一样,它更像一个“定点狙击”工具,需要你告诉它看哪个方法,它才能告诉你那个方法发生了什么。

5.2 从入口到出口:trace/watch组合还原完整调用链

但Arthas确实可以做到“跟踪URL调用路径”,只是你需要手工多走几步。核心思路是从入口方法开始,一层一层向下追踪。

首先找到HTTP入口。Spring MVC应用,入口自然是Controller层的方法。先用trace命令跟踪Controller方法,找出耗时最长的子调用:

trace com.example.controller.OrderController createOrder

执行这个命令后,你发起一次HTTP请求,Arthas会输出OrderController.createOrder方法内部所有子调用的耗时情况。你会发现耗时集中在了某一个Service方法上。然后针对这个Service方法继续trace:

trace com.example.service.OrderServiceImpl createOrder

如此逐层深入,直到你定位到具体的瓶颈点。这种方式虽然需要人工跟踪几步,但好处是极其灵活,你想从哪一层切入都行,没有APM那种固定框架的束缚。

如果你怀疑某个特定URL对应的入口方法不确定,可以先使用stack命令试探性地看调用栈:

stack com.example.web.CommonInterceptor preHandle

很多Spring Boot应用都有拦截器,preHandle会被所有请求触发。用stack命令在这个方法上监听,当请求进入时,Arthas会打印完整的调用栈,包含是哪个Controller方法在处理。这样你就拿到了URL到入口方法的映射关系。

5.3 tt命令在URL跟踪中的妙用

tt命令在处理“偶发问题”时有独特价值。场景是这样的:某个接口每天凌晨偶发报错,你看不到规律,也抓不到现场。这时你可以在可疑方法上挂上tt记录:

tt -t com.example.service.OrderServiceImpl createOrder

然后等待调用发生。每次调用都会被记录,产生一个索引号。等调用结束,你可以通过tt -i 索引号查看那一次调用的完整入参、返回值、异常堆栈。这就相当于给所有调用拍了快照,事后任意回放查看。

tt的进阶用法是结合条件表达式做精准采集。比如你只关注某个特定用户的请求:

tt -t com.example.service.OrderServiceImpl createOrder 'params[0].userId == 12345'

这样过滤掉无关调用,只记录目标用户的请求现场。这类操作日志和APM都很难实现同等精准度。

不过要留意,tt记录是有内存开销的,默认上限是10000条记录。在流量很大的接口上长时间挂tt,需要控制记录数量,或者用-n参数限制记录条数,避免内存膨胀。我的习惯是挂tt的时间不超过几分钟,拿到想要的现场就立即tt --delete-all清理。

5.4 跨线程调用怎么跟踪

还有一个进阶话题:URL请求经过线程池异步处理,调用链断裂了。Arthas默认的watch/trace只跟踪当前线程内的调用链,如果方法A把任务抛给线程池里的线程B执行,B里的方法调用A看不到。

处理这类问题有两个思路。第一种是在B的执行方法上单独挂Arthas命令看,把线程池执行的方法当作新的跟踪起点。第二种是借助一些Arthas版本里提供的--thread相关能力,在trace时尝试跨线程聚合。但整体上,跨线程场景Arthas不是强项,如果你大量使用异步编程,建议上APM工具专门解决链路追踪。

6. 进阶玩法:Arthas的脚本化与批处理

6.1 把常用诊断流程固化成脚本

Arthas的命令可以批量执行,这一点被很多人忽略了。你可以把一组命令写进脚本文件,用-f参数批量执行:

java -jar arthas-boot.jar --pid 12345 -f /path/to/diagnose.as

在diagnose.as文件里逐行写Arthas命令,按顺序执行。这对于团队内部沉淀诊断SOP非常有帮助。比如我处理线上慢接口,固定脚本大概是:

dashboard -i 1000 -n 5 thread -n 3 trace com.example.service.OrderServiceImpl createOrder

把这三条命令串起来执行,一次性拿到大盘数据、CPU热点线程和业务方法耗时分布。然后根据输出决定下一步深入方向。

6.2 用async命令实现异步执行

Arthas也提供了async命令,可以异步执行其他命令,把结果输出到指定文件。这在“现场稍纵即逝”的场景特别好用:

async "watch com.example.service.OrderServiceImpl createOrder '{params, returnObj}' -x 3 -n 5" -f /tmp/watch_result.log

这条命令让Arthas在后台持续监听,入参和返回结果写入日志文件。你可以先让监听跑起来,然后去复现问题,结束后查看日志文件。等于做了一个“临时日志探针”,但不需要改代码。

6.3 编写自定义诊断命令的思路

Arthas支持通过ognlvmtool等命令做很灵活的定制化诊断。比如你想查看某个类当前加载的所有实例数量,用vmtool:

vmtool -x 2 --action getInstances --className java.lang.String

虽然这个例子比较极端,但思路值得借鉴:把Arthas当成一个“能在JVM里执行任意代码”的调试终端,而不只是那几个内置命令。遇到内置命令覆盖不了的需求,用ognl和vmtool组合,往往能自己造出趁手的诊断工具。

7. 常见问题速查与独家排错心得

7.1 命令执行报错的排查速查表

现象常见原因排查路径
启动时找不到目标进程jps枚举失败、用户不一致、容器隔离确认ps -ef进程存在,改用--pid,容器内执行
attach之后提示拒绝连接目标JVM关闭了attach监听检查JVM参数是否有-XX:+DisableAttachMechanism
watch/trace命令无输出类名或方法名写错,或方法从未被调用sc命令确认类是否加载,用sm确认方法是否存在
ognl表达式报语法错误表达式写法不对或上下文变量不存在先写一个简单的ognl表达式测试环境,确认上下文变量名匹配
dashboard显示乱码终端编码问题设置-Dfile.encoding=UTF-8或调整终端编码
trace显示的调用耗时看不出问题方法内部子调用合并显示-n参数追踪多层,或用watch方法内联参数

7.2 一个完整的排查案例复盘

最后用一个真实排查案例串联一下前面的知识。某个订单服务的退款接口偶发耗时超过10秒,日志里没有异常,只有少量的超时警告。我的排查过程是这样展开的:

先用thread -n 3看CPU是否异常,排除了CPU飙高问题。然后在退款入口方法上挂trace,发现耗时主要集中在一个查询订单明细的Mapper方法上。继续trace这个Mapper方法,结果发现时间几乎全部花在等待一个数据库连接上。于是我再用thread -b查看是否有锁竞争,果然发现连接池的获取锁被其他线程持有。进一步ognl查看连接池配置,发现连接池最大连接数配置小于应用并发数。整个链路从现象到根因用了不到五分钟。

这就是Arthas的进阶用法带来的实际价值:你不光会执行命令,还要形成一套“由表及里、逐层下钻”的排查思维。命令是工具,思维才是核心。

7.3 长期稳定使用Arthas的几条心态建议

Arthas是好工具,但它不是万能的。它在生产环境操作时需要谨慎,尤其是resetstop全局卸载、以及类的重新加载这类高危操作,一定要判断好影响范围再执行。给生产环境用的机器上,建议固定Arthas版本,避免因为升级带来不必要的变化。

如果你所在的团队还没有人使用Arthas,建议先从低风险的线程排查、方法耗时统计这类场景入手,逐步建立起团队的诊断文化。等到大家都熟悉了,再推广到更复杂的字节码替换、热更新等高级场景。这样既能控制风险,又能让工具价值最大化。

最后再分享一个我自己的小习惯:每次用Arthas定位完一个问题,我都会把用到的命令和执行结果整理成一段文字,贴到项目的运维文档里。长期积累下来,这个文档就成了团队的“排障手册”,很多重复问题都能直接按图索骥,十分钟内搞定。这套组合拳打下来,Arthas就不再是一个命令行工具,而成了一套团队级的线上诊断资产。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询