ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Arthas实战:Java线上问题排查利器,无需重启即可诊断JVM

Arthas实战:Java线上问题排查利器,无需重启即可诊断JVM 真香定律放在技术圈一样适用。以前总觉得线上排查问题靠日志、靠监控、靠人肉加日志就够用了直到真正把 Arthas 用起来才发现以前那些操作有多绕。Arthas 是一个开源的 Java 诊断工具不用改代码、不用重启服务可以直接 attach 到正在运行的 JVM 进程上实时查看类加载情况、方法出入参、调用栈、线程状态、JVM 指标还能反编译和在线热更新。它专门解决一类问题线上偶发、日志不全、无法本地复现、一重启现场就没了。无论你是后端开发、维护过老项目还是正在被“线上 bug 怎么都复现不了”折磨这篇文章都值得花十分钟读完。1. 为什么说它是排查线上问题的“真香”工具1.1 传统排查方式的三座大山先聊聊大家最熟悉的排查流程。遇到线上故障第一反应通常是翻日志。但日志工具抄出来的往往是“结果”不是“过程”很多关键参数、临时变量、调用时序根本不存在。如果日志里什么都有那问题可能早就被监控平台发现了现实往往是日志不够、不够细、没有参数上下文。接着你会想本地复现把线上数据导出来构造请求打一遍结果发现本地怎么都复现不出来。因为并发量、缓存状态、时钟顺序、脏数据都可能影响最终结果本地环境永远只是线上环境的一种近似。最后一招是加日志重启改一行 log.info编译打包发版等流量进入再看日志这中间耗时少说半小时多则几个小时而且频繁重启会让现场被破坏流量重放也未必能还原问题。这三座大山我相信每个后端都经历过。Arthas 恰好能绕开它们不需要提前埋点因为它能在运行时直接查看真实出入参不需要反复发布因为它直接操作当前进程不需要靠猜因为它能把调用路径和方法执行细节摊开给你看。用过一次之后你会真心觉得“真香”。1.2 原理attach 与字节码增强要理解 Arthas 为什么能做到这些得先知道它的原理基础。Arthas 本质上利用了 Java 的 attach 机制和字节码增强技术。attach 是 JDK 提供的一种能力允许一个 JVM 进程运行时连接另一个 JVM 进程并通过 Agent 方式执行操作。字节码增强则是在类加载或运行时对已经加载的类做动态修改在方法执行前后插入拦截逻辑相当于给方法装上一套“临时探针”。这套探针是临时的JVM 一重启就没了不会影响原有逻辑。原理本身在 Java 生态里不算多新鲜但真正难的是把这一套能力封装得好用做成命令行交互式的产品。Arthas 做到的正是这一点从启动到使用几乎不需要在应用代码里增加任何依赖这对排查场景来说是极大的优势。1.3 什么时候该想到用它很多人问什么场景下 Arthas 才是刚需。我的判断标准很简单只要你遇到“日志看不出来、重启代价高、需要确认运行期状态”这三个条件中的两个就该考虑它。典型场景包括接口耗时突然变长、CPU 飙升、线程池被打满、缓存数据异常、灰度环境某个请求表现不一致、老项目没有源码无法重新编译。这里有一个容易被忽略的点Arthas 不是只给“救火”用的。在正常的压测、联调、性能分析阶段也可以主动用它。我经常在压测时开着 dashboard一边加压一边观察线程池大小、GC 频率和热点方法比事后看测试报告直观得多。因为它不是从统计数据里做推测而是直接从运行中的 JVM 里拿事实。1.4 为什么团队应该统一学习Arthas 这类工具个人会用和团队都会用的效果差别很大。一个人掌握遇到问题只能是他上团队都掌握任何值班的人都能快速定位初级问题不用每次都把核心开发叫起来看服务器。我见过不少团队把 Arthas 列为后端必修技能要求每个人至少掌握 dashboard、thread、watch、trace 四个命令的基本用法我觉得是非常务实的做法。2. 上手必须先掌握的 6 个高频命令命令速查表先放在前面后面会逐个细讲命令用途典型场景dashboard全局面板CPU、内存、GC、线程状态一眼看全watch观察方法出入参确认参数是否正常、异常在哪抛出trace方法调用链耗时定位慢请求卡在哪个子调用thread线程分析CPU飙高、死锁、线程阻塞jad反编译看线上实际运行的代码mc redefine热更新紧急修复后临时止损2.1 启动与选择进程java -jar arthas-boot.jarArthas 的使用路径非常直接。第一步下载 arthas-boot.jar放到服务器某个目录下执行java -jar arthas-boot.jar启动后它会自动列出当前机器上所有 Java 进程输入序号就能 attach。如果想跳过交互直接选进程可以在后面直接加 PIDjava -jar arthas-boot.jar 1234512345 是目标进程的进程号。刚上手时我建议大家执行 help 看看当前版本支持的命令列表因为不同版本之间命令细节会有差异老是照着老资料敲容易踩坑。有一个小细节Arthas 本身运行需要 JDK但需要 attach 的目标进程不一定要用 JDK 启动部分 JRE 环境也可以兼容。不过实际部署中建议用和运行环境同一版本的 JDK 去启动 Arthas 客户端减少无谓的兼容性问题。attach 成功后会进入交互终端提示符变成$所有命令都在这里输入。2.2 dashboard 一眼看全局attach 成功后的第一个建议命令是 dashboard。它会实时刷新一个面板展示 CPU 占用率、内存使用、GC 次数、线程状态分布、每个线程的 CPU 消耗等信息。这等于给 JVM 装了一个实时监控屏幕打开它就能快速判断问题在哪一层是 CPU 整体打满还是内存上涨触发频繁 GC还是某个线程卡死。初次使用的人容易被大量指标淹没我一般只看四样东西最上面的 CPU 使用率、内存区域变化、GC 频率、线程状态里 BLOCKED 和 WAITING 的数量。如果 GC 频率异常优先排查堆内存和对象分配如果有大量 BLOCKED 线程优先找锁竞争。面板是持续刷新的按 q 可以退出回到交互终端。2.3 watch 查看入参、返回值和异常watch 是线上排查使用频率最高的命令没有之一。它的作用是对指定方法观察当方法被调用时打印出入参、返回值或者异常信息。watch com.example.order.OrderServiceImpl createOrder {params, returnObj, throwExp} -x 2这条命令的意思是观察 OrderServiceImpl 类的 createOrder 方法当它执行时打印入参、返回值和异常对象嵌套展开深度 2 层。这里用了 OGNL 表达式用单引号包裹内置变量包括 params 数组、returnObj 和 throwExp。我第一次用 watch 排查问题时整个人的感觉就是开了天眼。以前要加日志重新发版才能看到的参数现在直接在当前进程里实时看到而且可以临时加条件只观察特定用户或特定业务类型watch com.example.order.OrderServiceImpl createOrder {params[0]} params[0].userId 10086 -x 3条件表达式写最后返回布尔值只有满足条件才打印。这样在流量大的接口下看不会被无关请求刷屏。2.4 trace 定位方法耗时分布如果说 watch 解决的是“参数对不对”trace 解决的就是“时间花在哪”。它会输出指定方法的调用链路上每个子方法的耗时自动计算总耗时和每个节点耗时执行效果类似一棵调用树trace com.example.order.OrderServiceImpl createOrder命令执行后目标方法一旦被调用你会看到类似下面的输出---createOrder() # 总耗时 823ms ---checkStock() # 56ms ---deductBalance() # 720ms ---sendMessage() # 47ms顺着耗时最大的节点继续往下 trace逐层钻取就能锁定真正慢的底层方法。这个过程就像剥洋葱把慢请求一层层拆到最小执行单元。有一点要提醒trace 是通过字节码增强实现的它只增强执行命令之后发生的调用历史调用不会回溯。所以想抓到想要的 trace 结果必须让目标请求再进来一次。如果接口触发频率低可以配合定时任务或者手动打一个测试请求。2.5 thread比 jstack 更实用的线程分析很多同学排查线程问题只会 jstack但 jstack 一下输出大量线程栈分不清哪个线程才是问题源头。Arthas 的 thread 命令可以直接按 CPU 消耗排序这个能力非常实用thread -n 3它会打印出 CPU 占用最高的 3 个线程同时带上对应线程栈。对比 jstack 还得自己去数线程 ID、对应 16 进制换算效率高了一个量级。它还能直接查死锁thread -bFound one Java-level deadlock: thread-1: waiting to lock monitor ...thread -b会直接输出检测结果和相关线程栈不用再从 jstack 输出里手工解析 Locked ownable synchronizers。如果需要看指定线程可以带线程 ID 精确查看这个在分析阻塞场景时很管用。2.6 jad mc redefine 实现在线热更新这一组命令要谨慎使用但在紧急情况下非常救命。jad 用于反编译已加载的类可以看线上运行的真实代码而不是本地仓库里可能已经过期的版本mc 是内存编译基于 jad 查看后的源码在内存里重新编译redefine 是把编译好的字节码替换到运行中的 JVM 里。jad --source-only com.example.order.OrderServiceImpl /tmp/OrderServiceImpl.java mc /tmp/OrderServiceImpl.java -d /tmp redefine /tmp/com/example/order/OrderServiceImpl.class热更新最典型的使用场景是线上有 bug 需要临时改一行逻辑发布流程要走审批这时先用 Arthas 做临时修复让服务先恢复然后马上走正规发布。要注意这个操作不会改磁盘上的 class 文件也不会写回 jar 包服务一重启就失效。而且不是所有代码都能热更新成功增加类、改变方法签名的场景通常会失败。3. 实战从几个真实故障看 Arthas 的实际用法3.1 场景一CPU 飙高快速定位到正则灾难性回溯我先讲一个最常见的 CPU 100% 问题。某服务在活动流量上来后CPU 使用率持续接近满核业务方反馈接口响应很慢。按老办法先看 top 找到进程 PID然后 jstack 导出线程栈再做 16 进制转换找到对应线程整个过程相当繁琐。用 Arthas 的流程会快很多。attach 之后先执行 dashboard整体看一眼 CPU 和线程状态当时很明显看到几个线程占用 CPU 非常夸张。然后执行thread -n 5它直接给出了 CPU 最高的 5 个线程及其调用栈。我看到一个线程栈反复停在同一个业务方法里跟进 trace 这个方法发现内部有一段非常复杂的字符串正则匹配在特定输入下触发灾难性回溯CPU 全部耗在了正则引擎内部。定位过程前后不到五分钟全程没有重启服务。定位到原因之后我用 jad 反编译出当前代码把正则逻辑临时替换成朴素的字符串前缀匹配通过 mc 和 redefine 热更新到线上进程服务立刻恢复。这个案例给我的最大感触是CPU 飙高和接口慢本质上都是“定位问题”的效率问题。只要能看到线程在干什么排查时间就能从小时级缩短到分钟级。3.2 场景二接口偶发超时trace 逐层揪出隐藏阻塞另一个案例是某接口偶发超时频率不高大概每天几次。这种偶发问题最折磨人日志里偶尔看到超时但不知道卡在哪个环节。我先用 watch 看接口入参发现超时请求和正常请求的参数没有明显区别于是改用 trace 观察调用链路。第一次 trace 还没有抓到超时等了差不多十几分钟才有一次超时出现。这次 trace 输出里接口方法总耗时接近 3 秒其中一个下游 RPC 调用的子节点耗时占 2.5 秒其他子节点都只有几十毫秒。顺着这个异步下游继续往下定位最后发现是某个配置中心客户端的刷新逻辑在特定时刻阻塞导致请求串行等待。这个例子想说明一个关键点很多偶发问题根本不体现在业务日志里因为业务代码里没有打印每一次下游调用的耗时明细。Arthas 的运行时观察能力等于给线上系统装了一台显微镜它能把正常日志看不到的细节全部拉到眼前。3.3 场景三验证修复方案不用发布就能试还有一种很常见的用法不是紧急救火而是验证方案。曾经有一个灰度问题我们怀疑是某个枚举映射导致数据错乱但不敢完全确定。改代码发布会话成本高而且如果改完还是错的等于白折腾。我先用 jad 反编译出线上实际运行的类确认枚举字段顺序和本地代码不一致这是一个非常关键的信息。因为本地代码经过多次迭代线上的 class 可能已经和 git 分支对不上。确认问题后我在本地改好用 mc 和 redefine 热更新到灰度进程再跑一次请求问题复现时已经不出现了这才放心走正式发布流程。很多人担心热更新有副作用。我的经验是先在测试环境或灰度环境做验证确认无误后再对生产操作操作完成之后立刻用 watch 或重新发一次请求确认行为符合预期。热更新在 JVM 层是支持的但生产环境的变更仍要符合团队变更流程最好有同事在场一起确认至少留一条操作记录。4. 常见问题与排查技巧实录4.1 attach 不上目标进程怎么办我遇到最多的问题是 attach 失败报错或者列不出进程。这里有一个速查表基本覆盖我遇到过的原因常见原因现象处理建议用户权限不一致attach 时报错或无权限切换成目标进程同一用户启动/tmp 空间满了attach 后立刻异常退出清理 /tmp 下临时文件目标进程已退出进程列表里找不到用 jps 确认进程状态端口被占用telnet 连接失败指定新端口启动Linux 下 attach 有权限限制最好用和目标进程相同的用户操作。Arthas attach 时会在 /tmp 写临时文件空间不足会导致各种奇怪问题。如果还是不行可以在启动 arthas-boot.jar 时带上--telnet-port和--http-port参数换两个没被占用的端口很多端口冲突的问题就解决了。4.2 对线上服务的影响到底有多大这是每个初次接触的人都会问的问题。字节码增强确实会带来一些性能开销但实际影响通常很小。我的实测经验是在常规业务方法上开启 watch 或 trace单次调用的额外开销在几毫秒以内对整个请求来说几乎感知不到。但如果是对超级热点方法开 trace比如某个每秒调用上万次的基础工具方法开销就会放大。这种基础方法被所有上层业务调用拦截和记录的成本会被放大到不可忽略。我的建议是精准选择目标方法不要对整个包路径做通配观察观察期间留意 dashboard 的 CPU 变化如果发现明显升高立刻缩小范围或者停掉观察命令。4.3 权限控制不能忽视Arthas 很强强到可以随时查看进程内数据、修改运行中的代码所以它也是一把双刃剑。在团队里使用至少要做到三件事控制在指定的运维和开发人员范围内使用不要全员开放服务器权限生产环境使用前提前申请临时权限并记录操作原因操作完成后及时退出交互终端不要长期挂着会话。有些团队会把 Arthas 封装成内部平台通过 Web 界面暴露受限接口避免开发人员直接登录服务器乱敲命令。我比较认可这种思路能力强大和可控并不矛盾。安全这块做到位工具才能长期用下去。4.4 部分命令在生产环境不可用跟具体 JDK 版本相关的问题也值得关注。某些 JDK 版本下 jad 反编译可能不完美尤其是涉及 Lambda 表达式或者动态代理生成的类时反编译结果会让人看得很困惑。redefine 的限制更多对新增成员变量、改变方法签名这类结构性变更通常不支持强行操作会报错。遇到这种情况不要死磕热更新。先用 watch 拿到足够信息确认根因后该走发布流程就走发布流程。工具的作用是帮你看清现场而不是替代正常的变更管理。5. 使用经验和技巧分享5.1 条件表达式让 watch 不再刷屏watch 默认打印所有请求在流量大的接口下输出会刷得飞快关键是里面有价值的请求可能只占很小比例。用条件表达式可以做到精准观察比如只看某类订单创建失败的请求watch com.example.order.OrderServiceImpl createOrder {params[0], returnObj} params[0].orderType 2 returnObj.success false条件表达式写在命令最后返回布尔值满足条件才触发打印。这个技巧能让你在繁忙环境下直接锁定目标请求不用从大量日志输出里去捞。5.2 ognl 命令主动触发静态方法调用有些时候你不仅想看还想主动触发某个方法比如清理某个静态缓存、查看某个配置项。Arthas 提供 ognl 命令可以不写代码直接执行静态方法调用ognl com.example.config.CacheManagerclear()这种操作在应急时很有效但风险也高它相当于直接往 JVM 里执行代码。我建的建议是先把命令在测试环境验证一次再上生产并且必须明确这个调用会影响什么、会不会有并发问题。5.3 脚本批量采集线程现场遇到周期性出现的问题比如每小时整点偶发一次卡顿单次执行 thread 不一定能抓到现场。可以用一条简单的 shell 循环把 Arthas 的批处理模式用起来while true; do echo --- $(date) --- thread_dump.log java -jar arthas-boot.jar PID -c thread -n 5 -i 1000 thread_dump.log 21 sleep 5 done这里用到了-c参数可以让 Arthas 执行一条命令后自动退出非常适合在脚本里批量使用。-i 1000是采样间隔单位是毫秒。把多次采集到的线程快照放在一起对比周期性问题的触发特征就很容易暴露出来。5.4 工具是帮手不是万能药最后说一点个人观察。Arthas 确实香但解决的是“运行态信息不可见”的问题很多线上故障的根因其实是代码逻辑、容量规划或者架构设计。工具能帮你更快定位问题但定位完之后该重构要重构、该扩容要扩容不能把热更新当成常态化修复方式。我现在的习惯是复杂问题先用 Arthas 快速观察现场拿到关键证据后把结论和操作记录整理到运维工单然后走稳定的修复流程。工具负责把定位时间从小时降到分钟剩下的工程质量问题还是要靠扎实的代码和可靠的变更流程解决。前后踩过几次坑之后我对 Arthas 的态度已经从“要不要学”变成了“怎么用得更稳”。如果你也被线上问题复现难、定位慢困扰别犹豫找个低峰期在测试环境把 watch、trace、thread 这几个基础命令练熟真到救火那一刻你会感谢这个“真香”工具。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表