Arthas实战:Java线上CPU飙高与接口超时的诊断排查指南

📅 发布时间:2026/10/19 8:51:22
Arthas实战:Java线上CPU飙高与接口超时的诊断排查指南
遇到线上Java服务CPU飙高、接口超时但日志里只有一行大概率的空指针你会怎么办以前我的第一反应是连服务器看日志、查线程dump看完还是不确定最后只能重启。Arthas这种Java线上诊断工具的出现彻底改变了我的排查节奏进程不用重启类和方法就像被装了探针一样随时可以看到入参、返回、异常和调用耗时。这篇内容主要面向Java后端开发、运维和SRE我会把自己在项目里使用Arthas的经验、常用命令和踩过的坑从头到尾讲一遍。适合第一次接触Arthas的人也适合已经用它处理过几次线上问题、想再系统看一遍细节的人。1. 从“重启大法”到“现场勘查”Arthas解决的三个线上死局先说句实话Arthas不是银弹但它精准打在“线上问题难复现、现场难保留”这个痛点上。遇到线上故障传统做法一般是三件套看日志、看监控、重启恢复。重启是最有效也最没用的办法因为它把现场破坏了问题很可能只被临时按下去过几天换个姿势又冒出来。1.1 死局一方法没打日志入参和返回全是黑盒很多线上问题只有“偶发异常”四个字。日志里记录的往往是框架层面的超时或错误业务方法内部到底拿到什么参数、走到哪个分支、返回了什么结果完全没有痕迹。这种情况想靠加日志重新发布来定位代价很大先等发布窗口、再等流量复现、然后又发现日志打得不够。Arthas的watch命令可以直接观测某个方法实时打印入参、返回值和异常不需要改动代码。它就像在运行中的进程外面套了一层透明壳方法每次被调用细节都能被看到。而且这类观测是瞬时的等拿到关键调用记录停止观测退出即可不会把太多探针留在业务代码上。1.2 死局二接口慢得明显却不知道慢在哪个环节接口P99总是上涨单个请求从几百毫秒涨到几秒业务方来找你第一句话是“你们接口是不是有问题”。你打开监控只能看到整体耗时中间调用了几个服务、一次查询耗时多少全是未知。Arthas的trace命令能把一个方法内部所有子调用拆开逐个统计耗时和次数。它不像APM那样需要提前埋点而是临时挂到运行中的方法上。比如某个下单接口慢trace下去经常能看到Redis耗时不高、数据库连接获取却占了大头这种结论不靠猜靠数字说话。1.3 死局三发布之后偶发类加载问题重启就“消失”线上出现NoSuchMethodError或ClassCastException且只在某台机器上出现最常见的原因是依赖jar版本不一致、类被多个ClassLoader重复加载。重启之后现象没了因为进程被调度到别的机器或者类加载顺序变了。Arthas的sc命令可以查看类加载信息和加载来源jad命令可以反编译JVM中实际加载的字节码一眼看出线上运行的是哪个版本的类。这种“现场还原”能力是日志和重启发版都做不到的。2. 从下载到attachArthas跑起来最容易被忽略的四个细节2.1 下载不是越新越好稳定版优先Arthas本身是一个Java agent通过arthas-boot.jar启动后会向目标JVM注入诊断服务。下载时我建议从官方Release页拿稳定版本别看到新版本就上生产。有的版本对JDK版本有要求尤其是JDK 17之后部分功能需要额外参数这一点很容易成为生产环境里的隐藏坑。直接把jar放到固定目录比如/data/ops/arthas后面排查问题时随时能复用不要每次临时去下载。2.2 attach之前先搞清楚目标JVM进程是谁启动Arthas最简单的方式是执行java -jar arthas-boot.jar启动后会把机器上的Java进程列出来输入对应序号就可以连上。也可以直接指定PIDjava -jar arthas-boot.jar 47172这里有个细节boot进程会有CPU和内存开销但它只负责注入和交互真正的agent运行在目标JVM内部。attach时确保运行Arthas的账号与目标进程账号权限匹配否则会attach失败。我遇到过不少次容器里JVM是root身份在跑自己却用普通用户去attach直接Permission denied排查半天才发现是身份问题。2.3 连接端口默认绑定本机别随便开放Arthas默认通过telnet端口3658提供命令行交互同时有一个HTTP端口8563提供Web Console。默认监听127.0.0.1这样相对安全。如果图方便把它绑到0.0.0.0等于把诊断权限开放给整个网络风险极大。生产环境上我始终建议保持本机绑定通过内部运维通道登录服务器后再进入Arthas操作。Arthas中有两个退出指令要区分quit是退出当前交互agent仍驻留stop是彻底关闭Arthas服务并恢复被增强的类排查完建议用stop收尾。2.4 第一次启动会下载依赖离线环境需要提前准备Arthas第一次运行会从发布站点下载配套的arthas-core和相关lib。很多生产环境没有外网此时可以把完整发行包放到服务器本地启动时指定本地路径。这块有点麻烦我的经验是准备一个内网文件目录把Arthas运行所需的一次性依赖全部放好新机器上直接引用。否则现场抓瞎明明工具在手里却连不上目标进程。3. 命令面板的背后逻辑从dashboard到trace该怎么选3.1 dashboard所有排查的第一步Arthas交互模式下第一优先记住dashboard。它展示的是整个JVM的实时总览线程总数、堆内存使用、GC次数和时间、类加载数量。它特别适合在刚接入时快速判断是不是内存吃紧、是不是GC频繁、是不是Full GC把业务线程都暂停了。比如CPU高进dashboard先看GC列如果老年代回收频繁问题方向就在内存分配和对象创建而不是某一行代码的死循环。这个命令相当于外科手术前的心电监护仪先看整体状态再决定切哪里。3.2 thread从总览落到线程线程是Java问题最常见的载体。thread -n 3按CPU占用率倒序打印Top线程的栈thread -b查看阻塞其他线程的“罪魁祸首”thread PID可以单独看某个线程栈。我发现很多人在CPU飙高时习惯用系统top -Hp再人工把线程号转成十六进制去线程dump里找其实Arthas里thread -n已经帮你按CPU排好序了直接就能看到栈顶在哪个方法。线程状态也要会看WAITING和TIMED_WAITING通常不是元凶RUNNABLE状态且栈停在热点代码里很可疑BLOCKED则要结合锁关系展开不要一看到WAITING就觉得是死锁。3.3 jad和sc线上代码到底是什么反编译给你看sc命令全称search class可以按类名模糊搜索。比如scTrade会返回所有名字带Trade的类加上-d参数可以看类加载器、类路径和加载来源。jad命令是反编译执行jad com.xxx.TradeService打印的是JVM中当前实际加载的字节码反编译结果。你可能会发现它和当前分支对不上这说明线上跑的代码根本不是你以为的那一版。这个命令解决过很多“我明明改过了”的争论。反编译结果和原始源码在变量名上会有细微差异这是正常现象不要拿它当源码评审工具它只负责还原现场。3.4 watch和trace从“看状态”到“看调用”watch是命令里的重头戏最简形式如下watch com.xxx.TradeService createOrder {params, returnObj, throwExp} -x 2 -n 5意思是观测目标方法打印参数、返回值、异常对象展开两层结构最多打印5次。它还支持条件表达式比如只观察满足某个金额阈值的调用避免被高频调用刷屏。trace则负责方法内部调用耗时trace com.xxx.TradeService createOrder #cost 300执行后每次调用都会输出内部各子调用耗时支持用#cost做耗时过滤这个特性非常符合“只关心慢请求”的排查直觉。monitor可以按固定周期统计方法的成功、失败次数和平均耗时相当于临时加了一个轻量监控。ognl更底层能直接读取静态字段、调用Bean方法适合在watch输出不够时再往深处挖一层。4. 实测CPU飙高线程号、线程栈和反编译三步定位到行4.1 第一次接触事故现场先别乱跑命令有次接到告警某个会员积分服务的CPU使用率连续5分钟超过80%接口成功率开始下降。登录服务器后我没急着进Arthas而是先用top整体确认进程情况再执行top -Hp PID找到CPU占用最高的线程比如线程号47189。传统流程是printf %x 47189拿十六进制线程号再去线程dump里找对应栈。现在的Arthas已经能简化这一步但理解这个原理有助于你判断工具输出的信息处于哪个层次。4.2 thread -n在手top可以退居二线进入Arthas后执行thread -n 3输出结果里按CPU占用排序。可以看到某个RUNNABLE线程栈顶停在了一个工具类的indexOf或正则匹配上。再执行thread 47189查看完整线程栈确认它其实是从一个批量导入任务里触发的。这里的关键信息是栈顶类名和行号它告诉你“现场在哪”但还没有告诉你“代码为什么写成这样”。4.3 jad反编译问题藏在一行正则里线程栈只提示了在哪个类的哪个方法还不够。接下来执行jad com.xxx.ExcelImportUtils查看线上实际字节码结果发现关键方法里每次循环都执行了Pattern.compile(regex)和matcher.find()。正则表达式编译一次就可能消耗几十到几百微秒在批量场景里循环几万次就变成CPU杀手。如果只靠加日志重发很难从日志里看出这行代码的代价但Arthas把线程栈和字节码摆在一起问题直接暴露连“复现”都不需要。4.4 修复之后怎么验证修改方式很简单把正则Pattern提取到static final字段或改用String的replace等更轻量的API。修复后先不急着退出Arthas可以用watch或monitor观察修复后的方法调用耗时确认CPU没有再上升。等观察一段时间后执行stop让Arthas退出并卸载增强。这个案例说明一个排查套路dashboard或top确认现象thread定位线程和栈jad确认实际代码watch或trace验证修复效果全程不需要重启进程。5. 偶发超时的两种打法条件watch和trace耗时过滤5.1 偶发问题的复现难题线上最磨人的不是一直挂而是“每天随机出现几次”。比如某个接口偶发超时监控图表显示零星几个点日志里只有客户端读超时。你加日志、发版问题反而不出现了。对于这种场景Arthas的优势在于可以在问题出现时“正好在现场”。但也不是一直挂watch就好高频方法每秒几百次全量观测会带来额外开销还会刷屏。这时应该给watch加上条件只捕获特定场景。5.2 条件watch怎么用比如怀疑某个订单查询在数据量大时变慢可以先观测watch com.xxx.TradeQueryService queryOrder {params, returnObj} -x 2 -n 20 params[0].size() 1000意思是每次调用只有参数列表长度超过1000时才打印。这样低频问题不会被高频调用淹没。还能对耗时做过滤watch com.xxx.TradeQueryService queryOrder {params, returnObj, throwExp} #cost 500这种表达式看起来有点复杂底层是OGNL求值#cost是内置变量。刚开始记不住没关系把模板保存下来照着改参数就行。重点是让观测“有方向”而不是把Arthas挂在生产接口上无差别输出。5.3 trace耗时过滤把慢定位到子调用条件watch能拿到现场数据但“为什么慢”还得trace。执行:trace com.xxx.TradeQueryService queryOrder #cost 500然后静默等待等下一次慢调用发生时输出会显示方法内部每个子调用的耗时某个数据库查询占400ms某个远程调用占120ms问题链路一目了然。如果子调用里还有更复杂的嵌套可以继续trace到下一层。Arthas的trace统计精度足够用于定位但要注意它采用观测式统计不应该在压测场景下全量开启否则结果本身会失真。5.4 类加载器冲突的另类偶发还有一类偶发是ClassNotFoundException且只在特定流量下触发。用sc -d com.xxx.SomeParser查看类加载器和加载路径再用jad对比字节码往往发现多个ClassLoader里存在同名类但实现版本不同。此类问题Arthas不能直接修复但能告诉你哪个jar、哪个版本、哪个类加载器在起作用之后再调整依赖、清理lib目录问题才能真正消除。处理类加载问题时要特别关注栈顶是系统类还是应用类这决定了下一步是查业务代码还是查框架配置。6. 实战之后的冷思考Arthas的边界、风险和我的固定流程6.1 不是所有问题都值得上ArthasArthas是诊断工具不是监控系统。它对方法做字节码增强会带来性能影响尤其在低延迟高并发接口上全量观测的副作用不能忽略。我的原则是先通过监控、日志和线程dump缩小范围再决定要不要用Arthas。CPU高、线程阻塞、类加载异常、偶发参数问题这四类最合适。已经明确是物理网络丢包或数据库慢SQL那应该先查网络和DB而不是上Arthas乱扫。6.2 有状态命令要克制Arthas提供mc、retransform、redefine等热更新能力可以在不重启的情况下替换类的字节码。这个能力看起来很爽但我在生产上基本只在“进程已经不可用且必须立即恢复”的时候才考虑而且用之前会先备份原类文件用之后在最近一个发布窗口立即用正常流程重新发布。字节码热替换的坑非常多新代码引用的其他类可能没有被替换导致ClassNotFound用了新依赖但ClassLoader没加载也可能直接报错。为了省一次重启引入一个更大的故障不值。6.3 访问权限是安全红线Arthas能读取内存、执行OGNL表达式能力极大相当于给你一个JVM内部管理员权限。如果端口暴露别人连上来不仅能看到敏感数据还能通过表达式改变运行状态。生产环境必须保证telnet和HTTP端口只监听本机通过内部运维通道登录后再操作不要在防火墙规则里随便放开3658和8563端口。这个安全意识和用jstack一样重要但往往没人提醒。工具越强越要管好它的入口。6.4 我沉淀下来的固定步骤现在我在项目里处理JVM线上问题基本是固定流程先看监控确认影响面登录服务器用dashboard观察GC、内存和线程概貌根据现象选thread或thread -n定位线程栈用sc和jad确认线上实际代码用watch或trace收集入参、返回和耗时拿到结论后尽快stop退出事后把关键输出保存到问题记录里。整个过程第一目标是“拿到现场”不是“当场修复”。只要现场数据完整后面的修复决策就简单很多。Arthas用得好不好不在于记住了多少命令而在于你面对一个模糊的线上问题时清楚下一步该用哪个命令收集哪一类信息。