1. 为什么需要Arthas:线上问题排查的痛点
1.1 传统排查方式为什么让人抓狂
先聊一个让所有Java后端开发者都头疼的真实场景:线上服务突然CPU飙升、接口响应变慢、内存持续上涨,或者某个方法偶尔报出让人摸不着头脑的异常。这时候大家第一反应是登录服务器,然后打开终端,试图靠jstack、jmap、jstat这类基础命令去定位问题。实际操作过的人都知道,这些JDK自带命令在简单场景下勉强够用,但一旦问题稍微复杂一点,效率会急剧下降。
举个例子,你想知道某个线上接口的入参到底是什么,传统做法是:先改代码、加日志、重新打包、上传服务器、重启服务、等待流量进来,再回去看日志文件。这一整套流程下来,顺利的话二十分钟到半小时,如果不顺利——比如安全部门不允许随便发版、或者问题只在特定偶发条件下出现——你可能一整天都耗在"改代码→发版→看日志"这个死循环里。更麻烦的是有些问题根本复现不了,你在测试环境怎么折腾都正常,一上生产就抽风,日志里又什么都没打,这时候如果没有趁手的诊断工具,基本就是两眼一抹黑。
另一个痛点在于:很多Java线上问题发生在已经运行的进程中,你不能随便停掉它,也不能为了排查问题就往生产代码里塞一堆临时逻辑。服务的稳定性是第一位,任何可能导致服务重启或者性能劣化的操作都要尽量避免。而传统的JVM调优命令,虽然能拿到线程栈、堆转储、类加载信息,但它们给出的都是静态快照——某一瞬间的状态,无法动态地观察方法调用、参数变化、返回值、异常抛出这些运行时行为。
Arthas这个工具之所以在开发者圈子里口碑这么好,就是因为它专门解决这些"静态快照搞不定、改代码发版又太重"的线上问题。它可以直接附着到目标Java进程上,在不重启服务、不改代码、不发版的情况下,实时查看类信息、方法调用详情、线程状态、GC情况、甚至是反编译线上代码来确认当前运行的版本到底改没改。对于一线开发工程师、运维工程师、以及负责排查线上故障的SRE来说,掌握Arthas基本上属于必备技能。
1.2 Arthas核心价值:线上"手术刀"而不是"重启键"
理解Arthas的价值,可以把它类比成一台不停机的手术设备。传统排查方式更像是"把机器关掉,拆开看零件",而Arthas是"机器还在运转,你拿着内窥镜伸进去,看清每个零件的工作状态"。它基于Java Agent机制实现,利用Instrumentation技术动态增强类的字节码,所以能在进程运行时实时注入诊断逻辑,再通过命令行交互界面把结果反馈给你。
作为一个长期在一线排查问题的人,我对这套机制的体会非常深。它最爽的点在于:你不需要预埋任何探针代码。以前有些团队会提前在关键方法里加耗时统计、参数日志,但现实是——你永远无法提前知道线上哪个方法会出问题。等你预埋的日志真派上用场的时候,多半是某次性能压测或者线上故障复盘时才意识到"哦,当时如果这里打个点就好了"。Arthas把这些事情变成了随时可用、按需开启的能力,用完即走,不会对业务代码产生任何污染。
另外,Arthas对业务侵入性极低,启动后默认不会影响业务流量,诊断完直接退出即可。它支持的Java版本范围也很宽,从Java 6到Java 17,主流企业的技术栈基本都能覆盖。随着微服务架构越来越普及,一个请求要经过好多服务节点,每个节点都可能成为瓶颈,Arthas这种"随时随地看运行时状态"的能力,在排查分布式链路中的单点问题时价值会更加明显。
2. 安装前的环境检查与准备
2.1 确认JDK环境:第一件必须做的事
这个环节其实最容易被忽略。不少新手拿到Arthas安装包就急着解压、启动,结果一头撞上诸如Unsupported class file major version或者进程根本无法attach的报错,才想起来去查Java环境。Arthas本身是一个Java应用,它的运行依赖JDK,目标进程也是一个Java进程,所以你的机器上必须有可用的JDK环境。
实际操作中,我建议用下面的方式确认环境:
java -version正常情况下会看到类似openjdk version "1.8.0_xxx"或者openjdk version "17.0.x"的输出。这里要特别注意的是,只装了JRE是不够的。Arthas在attach过程中需要用到JDK的一些内部工具类,比如com.sun.tools.attach.VirtualMachine,这些只在完整JDK里存在。如果你在服务器上只安装了JRE,执行java -version也能正常输出版本号,但接下来启动Arthas时会报tools.jar找不到或者 attach 失败的错误。
我在某项目现场就踩过这个坑。当时一台新扩容的服务器上为了方便只装了JRE,结果Arthas启动后一直报错说找不到Java虚拟机进程。后来排查了一圈才发现不是Arthas的问题,而是环境里压根没有JDK。所以这里给出一个明确的建议:在生产服务器上统一安装完整JDK,不要只装JRE,这不仅仅是Arthas的需求,像jstack、jmap、jstat这些排查命令同样需要完整JDK支持。
另外还需要确认一个细节:Arthas连接的Java进程必须和Arthas运行在同一个用户权限下。比如目标Java服务是用appuser账户启动的,那你也必须切换到appuser再去启动Arthas,否则会因为权限不足无法attach。关于这一点,后面专门讲启动失败的排查时会再展开。
2.2 下载与版本选择:别总是追最新
Arthas的安装包是一个压缩包,官方发布渠道会提供持续更新的版本,下载后是一个名为arthas-xxx-bin.zip的包,解压即可使用,不需要编译,也不需要配置额外的环境变量。这是它非常友好的一面——真正的免安装、解压即用。
版本选择上,我的建议是:如果服务器上跑的是老项目,优先选择与当前JDK大版本兼容的稳定版,不必追求最新。Arthas每次发版主要以修复Bug和新增命令为主,对于常规排查场景,稳定版足够用。特别是生产环境,一个经过了充分验证的稳定版本比刚发布的功能更让人放心。
下载方式一般有两种:一种是在有外网的服务器上直接用命令行工具下载,另一种是在本地下好之后通过跳板机或运维平台传到目标服务器。考虑到很多公司的生产服务器处于内网环境,我习惯在本地官网下载好对应版本的压缩包,检查好校验和(MD5/SHA256),再通过堡垒机推到服务器上。这样既安全又可控。
下载完成之后,建议把安装包统一放到一个固定的目录,比如/opt/arthas或者用户家目录下的~/arthas,不要随手丢在/tmp里,毕竟生产环境需要的是可管理的痕迹,而不是临时文件。
2.3 用普通用户操作:别用root硬刚
截图里"切换到普通用户"这个提示非常关键。很多朋友图省事,直接root用户把Arthas跑起来,结果要么报权限错误,要么后面排查问题时干扰信息太多。
从原理上讲,Arthas需要attach到目标Java进程,而attach机制要求当前用户必须和目标进程的启动用户一致,或者拥有足够的权限。如果你用root去attach一个普通账号启动的Java服务,虽然root权限足够,但实际使用中会引入一些权限变化带来的不确定因素。反过来,如果你的Java服务本身就是root启动的(这种情况在老旧项目里不少见),那用root操作Arthas也没太大问题,只是从安全规范角度而言,不建议服务用root跑。
我在日常运维中的标准操作是:先用ps -ef | grep java找到目标进程对应的启动用户,然后明确地用su - 用户名切换过去,再执行Arthas启动命令。这样不仅符合最小权限原则,还能大大减少"明明看到了进程却连不上"这类尴尬问题。特别是在多租户的服务器上,多个Java应用分属不同用户,搞混用户会导致Arthas只能看到当前用户下的Java进程,其他进程一概列不出来,这一点新手要格外注意。
3. 安装与启动全流程详解
3.1 解压安装包与目录结构
安装过程本身不复杂,把下载好的压缩包放到目标目录后,执行:
unzip arthas-xxx-bin.zip或者用tar -zxvf(如果下载的是tar包)。解压后你会得到一个目录,里面基本就几个东西:一个arthas-boot.jar,一个as.sh(Shell启动脚本),以及一堆lib底下的依赖jar包。核心要素就是arthas-boot.jar,后面所有操作基本都靠它。
你可以顺手验证一下解压结果:
ls -l arthas-xxx-bin正常情况下能看到arthas-boot.jar、as.sh、install.sh等文件。有些版本还带一个log目录,用来存放Arthas自身的日志。目录结构很清晰,不需要做额外的配置。
这里有一个容易被忽略的操作:检查解压后的文件属主。如果你是用root用户解压的,后续切到普通用户去执行时,可能会遇到目录没有读权限的问题。稳妥的做法是解压完以后直接把整个目录chown给目标用户:
chown -R appuser:appuser /opt/arthas因为我真的见过很多人在这一步卡住:启动脚本报各种没有权限的错误,排查了半天发现就是解压的时候文件属主不对。小细节,但很影响体验。
3.2 启动Arthas并连接目标进程
启动方式有两种:一种是直接用Java命令运行arthas-boot.jar,另一种是执行as.sh。推荐使用as.sh,因为它在背后帮我们处理了Java版本检测、PID选择等逻辑,用起来更省心。
如果Java服务就在本机,最简单的启动方式:
java -jar arthas-boot.jar回车之后,Arthas会扫描当前用户下的所有Java进程,然后列出数字列表让你选择。这个列表类似下面的输出格式:
[INFO] arthas-boot version: x.x.x [INFO] Found existing java process, please choose one and input the serial number: * [1]: 12345 com.example.controller.UserController [2]: 23456 org.apache.catalina.startup.Bootstrap输入对应的序号按回车,就完成attach操作。之后你会进入Arthas的命令行界面,出现$提示符,就可以开始执行各种诊断命令了。
如果你明确知道要连接的PID,也可以一步到位:
java -jar arthas-boot.jar 12345或者用as.sh:
./as.sh -p 12345按PID直连的方式在脚本化操作和日常快速排查中非常实用。还有一个细节:如果你在启动时遇到下载依赖慢或者提示从远程拉取组件的情况,说明这个版本首次运行时会联网解析某些框架依赖。在内网环境建议提前确认好依赖是否齐全,或者改用--local等方式限定本地模式。
3.3 后台运行与退出技巧
很多人误以为启动Arthas会一直挂在当前终端上,其实Arthas支持远程诊断模式——你在本地开发机上启动Arthas客户端,让它连接到远程服务器上的Java进程。不过在大多数场景中,我们是直接在目标服务器上用交互式终端来跑Arthas。
由于Arthas是交互式命令行工具,当你退出SSH时,Arthas客户端进程也会跟着断开(但注意,attach到目标进程的诊断代理不会一直残留,目标Java进程本身不受影响)。如果你想在后台持续收集某些信息,可以用batch模式和异步输出,比如把命令写到一个脚本文件里:
./as.sh -p 12345 -f /tmp/arthas_commands.txt这样Arthas会执行文件里的命令然后退出,适合定时抓取线程栈、监控方法耗时这类自动化场景。
退出Arthas客户端时,直接输入quit或exit即可。学弟学妹经常问我:退出Arthas会不会对业务进程有影响?答案是:不会。Arthas的字节码增强是在独立的代理中完成的,退出后只是停止诊断,不会导致业务代码回滚或者服务重启。不过有一个小坑:如果你用Arthas对某些类做了watch等高频率增强操作,长时间挂着确实可能带来一些性能开销,用完及时退出是好习惯。
4. 核心命令实战
4.1 dashboard:全局状态尽收眼底
dashboard是我每次连接到目标进程后敲的第一条命令。它有点像一个实时刷新的"服务器仪表盘",能把当前进程的全局状态一次性展示出来。执行:
dashboard界面上会分区域显示几类信息:线程列表及状态、内存堆和GC情况(包括各个分区的使用量、GC次数和耗时)、运行时信息(比如JVM版本、运行时长、类加载数量)。这些东西在你还没头绪的时候特别有用,能快速让你对进程的整体健康度有个判断。
举个例子,有一次某个接口的RT突然报警,我连上Arthas先跑了一个dashboard,看到老年代内存占用持续增长,而且FullGC次数在短时间内飙得飞快。这一下就把方向从"代码逻辑"引向了"内存分配和对象创建"的方向。如果只靠top看到CPU高,你根本不知道是什么在触发GC,而dashboard直接把这个因果链呈现出来。所以我会建议所有人:遇到性能问题,第一个命令永远是dashboard。
这里有个使用细节:dashboard默认是持续刷新模式的,像top一样每隔一段时间自动刷新一次。如果只是想看一眼当前状态,可以按q快速退出;如果想固定输出一屏,可以加参数控制刷新的次数和间隔,比如dashboard -i 5000 -n 3表示每5秒刷新一次,总共刷3次。这个技巧在做性能灰度对比时可以配合watch一起使用。
4.2 thread:线程问题定位的首选
线上服务最让人头疼的几类问题中,线程死锁、线程阻塞和线程数膨胀出现的频率最高。Arthas的thread命令比JDK自带的jstack好用太多,因为它直接给出了线程的实时状态和热点分析,还能找出CPU占用最高的线程。
查看所有线程的状态分布:
thread此命令会统计各状态的线程数,比如RUNNABLE、WAITING、TIMED_WAITING、BLOCKED。如果某个时刻大量线程卡在WAITING或者BLOCKED上,基本可以断定有锁竞争或者资源等待的问题。
如果要直接定位CPU占用最高的线程,一条命令就够了:
thread -n 3这个命令的作用是打印CPU占用率最高的前三个线程的详细栈。我以前排查过高CPU问题,用top找到PID后还得人工转十六进制、再去jstack里人肉搜索,费时费力,而thread -n 3一步到位把最可疑的线程栈打出来,通过栈里对应的业务代码位置基本就能锁定问题点。
查看指定线程的详细栈信息,可以用:
thread <线程ID>注意这里的线程ID是Arthas展示的ID,不是操作系统层面的PID。如果有线程死锁,直接执行:
thread -b它会自动检测并输出死锁相关线程的堆栈。我在实际项目中用过一次,两个服务互相持锁等待,秒级定位,效率提升非常显著。
4.3 jad:反编译确认线上真实代码
jad命令在排查"代码到底改了没"这类问题时价值极大。它在Arthas命令中的地位,相当于给我们配了一台"线上代码实时放大镜"。
使用方式很简单:
jad com.example.service.UserServiceImpl执行后,该类的字节码会被反编译成Java源码打印出来。你可能会问:为什么需要反编译,不是有源码仓库吗?因为线上运行的代码很可能和你本地分支里的代码不一致。有些公司的发布流程比较粗放,或者某个补丁是运维同学手工打上去的,代码仓库里的版本反而落后于线上。这时候jad看到的内容才是服务真正在执行的逻辑,能帮你确认问题到底是"代码逻辑本来就有问题"还是"部署版本不是你以为的那个版本"。
我印象很深的是一次我排查一个诡异的空指针异常。根据日志里的报错行号,我打开源码仓库里对应的类,怎么看逻辑都对,不会产生空指针。后来用jad反编译线上实际加载的类,发现那个类压根不是仓库里的那段代码——线上部署的是旧版本,方法逻辑里有一个历史遗留的空值判断漏洞。这一下就破案了,而且整个过程没有打扰到任何人。
需要注意的是,jad输出的源码是反编译产物,变量名可能会变得比较奇怪(比如a、b、param1),注释也全部丢失,但在确认执行逻辑和行号对应关系方面足够用了。
4.4 watch:观察方法调用与返回值
如果说前面的命令解决的都是"是什么状态",那么watch解决的是"这个方法到底干了什么"。它允许你监控某个方法的调用入参、返回结果、抛出的异常,甚至是方法执行耗时。这算是Arthas所有命令里使用频率最高的一个。
基础用法:
watch com.example.service.UserServiceImpl getUser (params[0]) (returnObj)这个表达式的含义是:当getUser方法被调用时,打印第一个参数和返回值。每次调用都会实时打印一行,格式非常直观,包含方法所在类的信息、调用耗时、入参值、返回值。调试线上问题的时候,你可以直接用watch去看某个方法收到的请求参数到底长什么样,而不用再去改代码打日志。
比基础用法更强大的是它支持条件表达式,只在满足特定条件时才输出。比如我只关心某个用户ID的请求:
watch com.example.service.UserServiceImpl getUser "params[0].id == 12345" (returnObj)这在流量比较大的接口上尤其有用——你不会希望每个请求的参数都刷屏,只关注出问题的那类请求就够了。
还有一个高频场景是看某方法的异常抛出情况:
watch com.example.service.OrderServiceImpl createOrder -e "throwException()"在偶发异常排查中,这个命令能让你捕捉到异常发生时的调用栈和上下文,非常实用。不过要说一句:watch使用的是字节码增强技术,在高频调用的方法上开着watch会引入一定的性能损耗,所以用完一定要stop关闭或者watch命令自身也提供了执行次数上限(-n参数),设置一个合理的采集次数可以避免影响线上服务。
4.5 sc:查找已加载的类
sc(search class)的作用是在JVM已加载的类中搜索符合条件的类。它可能不像watch、jad那么常用,但在某些场景中是不可替代的。
比如你怀疑某个类是否被加载、或者某个第三方jar包中的类是否生效,执行:
sc -d com.example.service.UserServiceImpl-d参数表示输出类的详细信息,包括类加载器、类所在jar包路径、类的修饰符等。这能帮你确认:线上到底加载的是哪个版本的类、是通过哪个类加载器加载的。
遇到某些莫名其妙的 "ClassCastException" 或者 "NoSuchMethodError" 时,sc -d查一下类加载器信息和jar包路径,通常能很快发现是同一个类被多个不同版本jar包加载导致的冲突。特别是现在工程依赖庞大,各种传递依赖很容易把同一个类打入多个jar包,这种问题单纯看代码很难看出来。
另一个常见用法是模糊搜索:
sc *controller*这会搜索所有名字中包含controller的已加载类,适合快速定位某个组件的实际类名。在一个你不太熟悉的新项目里,用这个命令来梳理接口类和实现类的分布非常高效。
4.6 更多常用命令一览
除了上面四个高频命令,Arthas还有几个命令在特定场景下非常有用,我把它们整理成一张表,方便大家参考:
| 命令 | 作用 | 典型场景 |
|---|---|---|
trace | 跟踪方法内部子调用路径和耗时 | 定位慢接口中具体哪一步耗时最长 |
jad | 反编译类代码 | 确认线上实际运行的代码逻辑 |
monitor | 统计方法的调用次数、失败次数、平均耗时 | 观察某个接口的整体健康度 |
classloader | 查看类加载器信息、加载的jar包 | 排查类冲突、NoClassDefFoundError |
ognl | 动态执行表达式、调用方法 | 查看静态属性、调用某些隐藏方法 |
stop | 关闭Arthas并撤销全部增强 | 结束所有诊断动作 |
trace在定位慢方法时是神器级别的。它能展示目标方法内部调用了哪些子方法、每个子方法耗时多少毫秒。搭配trace 类名 方法名使用,可以直接看到耗时分布——比如总耗时2秒,其中数据库调用占了1.8秒,网络调用只占0.1秒,几十秒就能定位到底慢在哪一层。
ognl命令稍微进阶一些,它可以在线执行一段表达式来读取对象信息。比如你想查看某个静态变量的当前值,用:
ognl '@com.example.config.GlobalConfig@MAX_RETRY'这种"不用改代码就能实时读取内部状态"的能力,在调试一些只会在特定配置下才出现的问题时极其有用。
5. 常见问题与排查技巧
5.1 启动失败和连接失败的典型原因
Arthas使用过程中,最常遇到的问题集中在"启动不了"和"进程连不上"这两类。下面整理几个高频场景和对应的解决方案,都是我亲手踩过的。
场景一:Arthas提示找不到Java进程
执行java -jar arthas-boot.jar后,输出的进程列表是空的。原因90%是当前用户权限问题。前面提过,Arthas只能列出当前操作系统用户启动的Java进程。如果你的Tomcat或者SpringBoot服务是用root启动的,而你现在用appuser执行Arthas,那列表里当然看不到目标进程。解决办法就是切换到启动该Java服务对应的那个用户。
场景二:提示无法attach,或者抛出AttachNotSupportedException
这种情况多数是因为目标环境只安装了JRE而没有完整的JDK,或者是使用了某些非标准JDK发行版。Arthas attach时需要用到tools.jar或者attach相关的API,在纯JRE环境中这些内容是缺失的。另外需要注意,从JDK 9开始,内部模块机制有了变化,某些老版本的Arthas在很新的JDK上可能会有兼容性问题,这时要考虑升级Arthas版本。
场景三:连接成功后,命令执行报错java.lang.UnsupportedOperationException
这个问题大多发生在老版本的Arthas上,遇到增强类或某些被保护的JDK内部类时无能为力。最直接的思路是升级Arthas到新版本,新版本对JDK 11+兼容性做了大量优化。
场景四:启动时报端口被占用
Arthas启动时会启用一个本地端口用于通信,常见端口是3658和8563。如果你同时启动了多个Arthas实例,端口可能发生冲突,此时可以指定端口启动:
java -jar arthas-boot.jar --telnet-port 3659 --http-port 8564这个问题在多人共用服务器分布式排查时比较常见,到后面会越用越熟练,但第一次碰到往往会蒙。
5.2 使用中的性能影响和注意事项
很多人担心Arthas会不会拖垮线上服务。这个担心是合理的,但Arthas的设计本身已经把对业务的影响降得很低。默认的dashboard、thread、jad等命令都是只读操作,不会对类的字节码做任何修改,所以性能影响几乎可以忽略不计。而watch、trace、monitor这类命令会进行字节码增强,在方法入口和出口插入拦截逻辑,确实会带来额外的性能开销。
所以实际使用时,我给自己定了三条规矩:
第一,能不用增强类命令就不用。先跑dashboard、thread、jad做静态观察,尽量缩小问题范围,再决定是否用watch和trace。
第二,用增强类命令必须加执行次数限制。给watch加上-n 2表示只观察两次就自动停止,不要一直开着。
第三,用完立刻stop。这是一个危险的习惯:很多人退出Arthas时只是输入了quit退出客户端,以为万事大吉。实际上,quit只是断开客户端连接,attach进去的服务端可能还在运行,字节码增强也可能没撤销。正确的退出方式是:
stop执行stop会完全关闭Arthas服务端,并撤销所有字节码增强,让目标JVM恢复到原状。这个细节我每次培训都会强调,但总是有人忽视。
5.3 Arthas脚本化与自动化集成的思路
除了手动交互模式,Arthas也支持以批处理方式执行命令,这是实现自动化诊断的关键。比如之前提到的:
./as.sh -p 12345 -f /tmp/commands.txt这个模式特别适合定期执行的信息采集任务。举个例子,你可以写一个命令文件,内容为:
dashboard -n 1 thread -n 3 quit然后通过crontab定期执行,把输出重定向到日志文件:
0 * * * * /opt/arthas/as.sh -p 12345 -f /opt/scripts/check.txt >> /var/log/arthas_check.log 2>&1这样就可以在一个时间维度上持续记录线程和内存的变化趋势,为性能调优积累数据。不过要注意的是,自动化执行需要确保PID固定或动态获取。如果PID会变化,可以先把PID写入一个文件,在脚本里读取。
5.4 结合监控平台的扩展用法
再往深了说,Arthas的输出其实非常适合接入现有的监控告警体系。虽然它定位的是"单机单进程"的运行时问题,但在集群环境下,我们可以在多台机器上同时运行Arthas命令,把结果汇总到统一的日志采集平台,再配合原有的业务监控数据做交叉分析。某次大促前压测中,我就是利用批处理模式同时抓取了多台容器的线程栈和GC日志,几百台机器的问题瞬间就能看出共性来。
这种用法还带来一个额外的好处:因为Arthas本身是绿色解压的,你可以把它集成到自动化运维平台的"一键诊断"功能中。运维人员遇到告警,不需要登录机器敲命令,在平台上点一下按钮,后台就用-f模式跑一遍收集脚本,把报告直接输出到工单系统。这样做既降低了使用门槛,也保留了完整的诊断轨迹,非常实用。
6. 实操心得与避坑经验
6.1 我第一次在生产环境使用Arthas的完整复盘
回忆第一次真正在核心生产环境用Arthas,是在某次大促活动中。当时线上订单服务突然出现大量的超时告警,受影响的是核心的下单链路。团队的常规手段已经用了一遍:top看到CPU有一个核接近满载,jstack抓线程栈却发现大量的线程都在等待同一个数据库连接池的获取。看起来像是数据库连接池不够用,但DBA那边反馈数据库负载并不高,这就很矛盾了。
我冷静下来,用Arthas连上出问题的服务,先用thread -n 1看到CPU占用最高的线程,栈里果然停在获取连接的地方。再看dashboard,发现线程数异常高,而且新增了大量WAITING状态的线程。按理说这应该是连接池配置太小导致的,可为什么数据库负载不高?
接下来我用watch观察了连接池获取连接的方法,发现很多线程获取连接之前要进行一个外部HTTP调用来做租户鉴权,而这个调用超时时间设成了30秒,导致线程大量堆积在等待响应的状态,连接池里的连接被这些慢请求占满了,后续请求全部排队。问题根源根本不是连接池大小,而是上游服务响应太慢,拖垮了整个线程池。
那天是靠Arthas一步步定位到问题链路的。如果没有watch看到方法内部调用的时序,单靠jstack真的很难把"连接池等待→外部HTTP慢调用→线程堆积"这条因果链串起来。这次经历让我养成了一个习惯:遇到性能问题,先用Arthas快速做链路拆解,别急着凭经验下结论。
6.2 生产环境使用Arthas的安全红线
Arthas能力太强,用得好是利器,用不好就是事故。总结几条我在团队内部反复强调的注意事项:
- 任何写操作、动态修改类的功能(比如自定义classloader重载),非必要场景不要在生产环境尝试。
- 涉及字节码增强的命令,尽量加
-n次数限制,避免忘记关闭造成持续的额外开销。- 在生产环境执行
watch、trace前,先评估目标方法的调用频率,高频方法慎用。- 对核心服务操作之前,能走审批就走审批,保留操作日志和录屏留证。
- 退出Arthas时,永远记得敲
stop,而不是仅仅quit。
这些都是用真金白银换来的教训。曾有一位同事在线上用watch监控一个每秒调用上万次的方法,结果忘了加次数限制,半小时后接口RT恶化,用户开始出现超时。最后排查到就是因为Arthas的增强逻辑在高频调用下产生了显著开销。这个案例后来成了团队内部的新人培训材料。
6.3 进阶技巧:把Arthas用出效率
如果你已经能熟练使用上面这些命令,接下来可以尝试几个进阶技巧,让Arthas用得更顺手。
第一,善用命令别名和快捷键。Arthas交互界面下可以用Tab自动补全命令和参数,这是很多人不知道的细节。在完整类名比较长的情况下,Tab补全能省下大量打字时间。另外还可以通过set命令自定义一些配置项,比如关闭高亮、调整分页等,根据个人喜好优化体验。
第二,配合ognl做深层次状态查看。比如运行中的服务有一个框架级别的缓存Map,你想看看它当前有多少条目、甚至手动清空它,都可以通过ognl表达式完成。虽然写起来有学习成本,但一旦熟悉了语法,它带来的自由度是其他命令望尘莫及的。
第三,离线诊断思路。某些特殊情况下,目标服务器的网络环境不允许你实时交互,或者故障发生在深夜你不便操作。这时候可以抓取Arthas的输出日志,后续再复盘。Arthas自身有一个日志目录,在启动时会输出日志路径。analyse时可以通过这些日志还原当时的诊断数据。
最后再分享一个实用小技巧:如果你在一个项目组里经常要帮别人排查问题,不妨把最常用的几个诊断命令写成一个初始脚本,连接到进程后一键执行,把关键状态全部打印出来。这样既统一了排查流程,也让新同学能快速上手,不至于在故障发生时手忙脚乱。
Arthas的安装只是第一步,真正的价值在于你怎么把它的命令组合起来形成一套排查方法论。希望这篇内容能帮你少踩一些我当年踩过的坑,真正把这款工具用好。