三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

【JVM原理详解】53-Arthas线上诊断实战

【JVM原理详解】53-Arthas线上诊断实战

Arthas 线上诊断实战

引言

JDK 内置工具(jstack、jmap)能解决"看什么"的问题,但很多时候我们需要"看代码怎么跑的"——某个接口为什么慢、某段逻辑走了哪个分支、方法参数和返回值是什么。传统方案只能加日志、重新发布,在问题难以复现的线上环境几乎不可行。

Arthas(阿尔萨斯)是阿里开源的 Java 线上诊断工具,通过字节码增强技术,在不重启应用的前提下实现方法级观测、动态修改代码、火焰图分析等能力。它已成为国内 Java 开发者排查线上问题的标配工具。

本篇将系统讲解 Arthas 的安装、核心命令及完整实战排查流程。

Arthas 安装与启动

快速安装

# 方式一:下载 arthas-boot.jar(推荐)curl-Ohttps://arthas.aliyun.com/arthas-boot.jar# 方式二:使用官方安装脚本curl-Lhttps://arthas.aliyun.com/install.sh|sh

启动与 attach

java-jararthas-boot.jar

启动后会列出当前机器上所有 Java 进程,输入序号选择目标进程:

* [1]: 12345 org.example.MainApp [2]: 23456 com.example.Application Please input your choice:

也可以直接指定 PID:

java-jararthas-boot.jar12345

工作原理

Arthas 启动后会通过Attach API连接目标 JVM,然后利用Instrumentation API动态修改已加载类的字节码,织入观测逻辑。这种机制决定了:

  1. 无需重启应用:字节码在运行时被增强
  2. 有轻微性能开销:被观测的方法会有额外执行逻辑,但未观测的方法不受影响
  3. 退出后自动恢复:Arthas 退出时会清除增强的字节码,应用回到原始状态

核心命令详解

dashboard:实时概览面板

[arthas@12345]$ dashboard

dashboard 以全屏方式展示 JVM 的实时状态,每隔几秒刷新一次,内容分四块:

┌─ Thread ────────────────────────────────────────────────────────────────────┐ │ ID NAME GROUP PRI STATE %CPU DELTA TIME │ │ 1 main main 5 RUNNABLE 0.0% 0.00 10.2s │ │ 42 http-nio-8080-exec-1 main 5 WAITING 0.0% 0.00 2.1s │ │ 43 http-nio-8080-exec-2 main 5 RUNNABLE 12.5% 0.12 5.3s │ └─────────────────────────────────────────────────────────────────────────────┘ ┌─ Memory ────────────────────────────────────────────────────────────────────┐ │ heap used: 1.2G total: 2.0G max: 2.0G GC: G1 │ │ g1_eden used: 80M total: 200M │ │ g1_old used: 1.1G total: 1.5G │ │ nonheap used: 120M total: 256M │ └─────────────────────────────────────────────────────────────────────────────┘ ┌─ GC ────────────────────────────────────────────────────────────────────────┐ │ gc(count) gc(time) avg │ │ g1.young 15 0.234s 15ms │ │ g1.old 3 0.567s 189ms │ └─────────────────────────────────────────────────────────────────────────────┘ ┌─ Runtime ───────────────────────────────────────────────────────────────────┐ │ os.name Linux │ │ java.version 11.0.20 │ │ JVM start time 2026-07-17 10:00:00 │ └─────────────────────────────────────────────────────────────────────────────┘

dashboard 适合问题初步定位——一眼看到 CPU 最高的线程、老年代使用率、GC 频率。

thread:线程分析

# 查看所有线程thread# 查看 CPU 占比 TOP 5 的线程thread-n5# 查看指定线程thread43# 查看阻塞线程thread--stateBLOCKED# 查看死锁thread-b

thread -n 5输出示例:

threads Total: 42, NEW: 0, RUNNABLE: 5, BLOCKED: 0, WAITING: 30, TIMED_WAITING: 7 "Thread-43" Id=43 cpuUsage=12.5% deltaTime=0.12s time=5.3s RUNNABLE at com.example.service.OrderService.process(OrderService.java:120) at com.example.controller.OrderController.create(OrderController.java:45) ...

thread -b检测阻塞其他线程的线程——即持有锁导致他人等待的线程,对锁竞争排查非常有用:

"Thread-43" Id=43 BLOCKED on java.lang.Object@7b0a1a28 owned by "Thread-12" Id=12

jad:反编译类

# 反编译指定类jad com.example.service.OrderService# 反编译指定方法jad com.example.service.OrderService process

jad 用于确认线上运行的代码版本是否与预期一致。常见场景:发了版但怀疑代码没更新、怀疑类被其他 Agent 增强、排查 AOP 织入是否生效。

输出会显示类加载器和反编译后的源码:

ClassLoader: +-sun.misc.Launcher$AppClassLoader@18b4aac2 public class OrderService { public Order process(String orderId) { // 反编译的代码 } }

watch:方法执行数据观测

watch 是 Arthas 最常用的命令之一,用于观察方法的参数、返回值、异常、执行耗时

# 基本语法watch<类全限定名><方法名><观察表达式># 观察方法参数和返回值watchcom.example.service.OrderService process"{params, returnObj}"# 观察参数 + 返回值 + 耗时watchcom.example.service.OrderService process"{params, returnObj, #cost}"# 条件过滤:只看耗时超过 200ms 的调用watchcom.example.service.OrderService process"{params, returnObj}""#cost > 200"# 只看抛异常的调用watchcom.example.service.OrderService process"{params, throwExp}"-x2

输出示例:

method=com.example.service.OrderService.process location=AtExit ts=2026-07-17 11:00:00; [cost=350.2ms] result=@ArrayList[ @Object[][ # params @String[ORD123456], ], @Order[ # returnObj id=@String[ORD123456], amount=@BigDecimal[99.50], ], ]

关键参数说明

  • {params, returnObj, #cost}:观察表达式,用 OGNL 语法访问参数、返回值、耗时
  • #cost:方法执行耗时(毫秒)
  • -x 2:展开层级,控制输出深度(默认 1,建议设 2~3)
  • location:观测点,支持AtEnter(进入)、AtExit(正常返回)、AtExceptionExit(异常返回)

trace:方法调用链耗时

trace 用于追踪方法内部所有子调用的耗时,帮助定位方法中哪一步最慢。

# 追踪方法调用链trace com.example.service.OrderService process# 只看耗时超过 100ms 的调用trace com.example.service.OrderService process'#cost > 100'# 追踪多次调用trace com.example.service.OrderService process-n5

输出示例:

`---[350.2ms] com.example.service.OrderService:process() +---[0.5ms] com.example.util.Validator:check() #35 +---[280.1ms] com.example.repo.OrderRepo:query() #36 +---[65.3ms] com.example.service.PricingService:calc() #38 +---[3.2ms] com.example.service.OrderService:save() #42 `---[0.8ms] com.example.service.NotificationService:notify() #45

一眼看出OrderRepo.query()耗时 280ms,是性能瓶颈。trace 默认追踪两层调用,可通过--skipJDKMethod false显示 JDK 方法调用。

stack:查看方法调用栈

stack 用于查看谁调用了某方法,输出调用栈。

# 查看 process 方法被谁调用stack com.example.service.OrderService process# 条件过滤stack com.example.service.OrderService process'params[0].length > 10'

输出示例:

ts=2026-07-17 11:00:00; thread_name=http-nio-8080-exec-1; id=43; is_daemon=false; priority=5 @com.example.controller.OrderController.create() at com.example.controller.OrderController.create(OrderController.java:45) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at org.springframework.web.method.support.InvocableHandlerMethod.invoke(...) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067) ...

stack 适合排查"某方法被意外调用"的问题——比如某接口被大量调用但不知道来源。

profiler:火焰图

Arthas 集成了async-profiler,可生成火焰图,用于 CPU 性能分析。

# 启动 profiler,默认采样 CPUprofiler start# 采样 30 秒后停止profiler stop--formathtml--file/tmp/flame.html# 生成 svg 火焰图profiler stop--formatsvg--file/tmp/flame.svg# 查看支持的事件类型profiler list

生成火焰图后用浏览器打开,横向是调用栈,宽度代表 CPU 占比,最宽的"火焰"就是热点。

async-profiler 基于perf_events(Linux)或JFR(JDK 11+),相比传统的采样 profiler 更准确,且不会出现Safepoint Bias(只在安全点采样导致的偏差)。

redefine/retransform:热更新

Arthas 支持运行时替换已加载类的字节码,用于紧急修复线上 Bug。

方式一:redefine
# 1. 反编译当前类jad com.example.service.OrderService --source-only>/tmp/OrderService.java# 2. 修改代码(编辑 /tmp/OrderService.java)# 3. 编译为 class(可用 mc 命令)mc/tmp/OrderService.java-d/tmp/# 4. redefine 替换redefine /tmp/com/example/service/OrderService.class
方式二:retransform
# retransform 使用 Arthas 字节码增强后的 classretransform /tmp/OrderService.class# 查看历史 retransform 记录retransform-l

风险提示

  • redefine 是直接替换字节码,Arthas 退出后不会自动回滚(与 watch/trace 不同)
  • 不能修改方法签名、字段定义、类继承关系,只能修改方法体
  • 生产环境慎用!建议仅用于紧急止血,修复后仍需正常发布版本

实战:线上接口耗时排查完整流程

问题背景

某电商系统下单接口/api/order/createP99 耗时从 200ms 飙升到 2s,日志无明显异常,GC 正常。需要在不重启服务的前提下定位瓶颈。

第一步:全局概览

# attach 到目标进程java-jararthas-boot.jar12345# 查看概览dashboard

发现 CPU 和内存正常,但http-nio-8080-exec-*线程频繁出现,状态多为 RUNNABLE。

第二步:定位耗时线程

# CPU TOP 5 线程thread-n5

发现http-nio-8080-exec-3CPU 占比最高,栈在OrderService.create方法。

第三步:追踪方法调用链

# 追踪 create 方法的内部调用耗时trace com.example.service.OrderService create-n3
`---[1850.2ms] com.example.service.OrderService:create() +---[0.5ms] Validator:check() +---[1200.3ms] OrderRepo:queryOrder() # 慢! +---[620.1ms] PricingService:calcPrice() # 慢! +---[25.0ms] OrderService:save()

发现OrderRepo.queryOrder()耗时 1.2s,PricingService.calcPrice()耗时 620ms。

第四步:深入观测慢调用

# 观察 queryOrder 的参数和返回值watchcom.example.repo.OrderRepo queryOrder"{params, returnObj, #cost}""#cost > 500"-x2
method=com.example.repo.OrderRepo.queryOrder location=AtExit ts=2026-07-17 11:05:00; [cost=1150.3ms] result=@ArrayList[ @Object[][ # params @String[ORD_20260717_001], ], @Order[ # returnObj id=@String[ORD_20260717_001], items=@ArrayList[isEmpty=false;size=1500], # 1500 个商品! ], ]

发现返回了 1500 个商品的订单——这是个异常大的订单。

第五步:定位 PricingService 慢的原因

# 追踪 calcPrice 内部trace com.example.service.PricingService calcPrice-n3
`---[620.1ms] com.example.service.PricingService:calcPrice() +---[600.5ms] DiscountService:batchCalc() # 批量折扣计算 +---[15.2ms] TaxService:calc() +---[4.1ms] PricingService:aggregate()

继续深入:

# 观察 batchCalc 参数watchcom.example.service.DiscountService batchCalc"{params, #cost}"-x1

发现batchCalc接收了 1500 个商品做批量折扣计算,内部有嵌套循环导致 O(n²) 复杂度。

第六步:确认根因

# 查看调用来源stack com.example.service.DiscountService batchCalc

确认是上游传入了超大批量订单。结合日志发现是某促销活动触发了合并下单逻辑。

第七步:火焰图辅助验证

# 采样 60 秒生成火焰图profiler start# 等待 60 秒...profiler stop--formathtml--file/tmp/flame.html

火焰图中DiscountService.batchCalc占比最高,与 trace 结论一致。

第八步:紧急止血

# 确认当前代码jad com.example.service.DiscountService batchCalc --source-only>/tmp/DiscountService.java# 修改:加入批量分片逻辑(每 100 个一批)# ... 编辑代码 ...# 编译mc/tmp/DiscountService.java-d/tmp/# 热更新redefine /tmp/com/example/service/DiscountService.class

修复后 P99 恢复到 200ms 以内,后续通过正常发布流程固化修复。

实践要点

使用注意事项

  1. 性能开销:watch/trace 会对目标方法织入逻辑,高 QPS 接口上长时间开启可能影响性能。建议设置-n限制执行次数,排查完立即stop

  2. OGNL 表达式:watch/trace/stack 的条件过滤使用 OGNL 语法。常用变量:params(参数数组)、returnObj(返回值)、throwExp(异常)、#cost(耗时)、target(当前对象)。

  3. 条件过滤避免噪音:高频调用不加过滤会刷屏。用#cost > 100params[0].length > 10等条件精准过滤。

  4. 退出与清理stop命令会清除所有增强并退出 Arthas。切忌直接 kill 进程,可能导致增强的类未恢复。正确退出方式:

    # 先清除增强reset# 再退出stop

安全与权限

  • Arthas 能查看和修改运行时代码,等同于拥有应用完全控制权,生产环境务必限制使用人员。
  • Arthas 通过 Unix Domain Socket 或 TCP 通信,建议不在公网环境暴露。如需远程使用,通过 SSH 跳板机登录服务器执行。
  • redefine修改的代码不会持久化,重启后会丢失。务必在热修复后通过正式发布固化修复。

常用命令速查

命令用途示例
dashboard实时概览dashboard
thread -n 5CPU TOP5thread -n 5
thread -b阻塞线程thread -b
jad反编译jad com.example.Service
watch观测方法watch cls method "{params,returnObj}"
trace调用链耗时trace cls method '#cost > 100'
stack调用栈来源stack cls method
profiler火焰图profiler start; profiler stop
sc查找类sc -d com.example.Service
sm查找方法sm com.example.Service method
monitor方法统计monitor cls method -c 10
logger动态日志logger --name ROOT --level DEBUG
redefine热更新redefine /tmp/cls.class

小结

  • Arthas通过字节码增强实现运行时方法级观测,是排查"代码怎么跑"问题的利器,弥补了 JDK 工具无法深入方法内部的短板。
  • trace + watch 组合是排查耗时问题的标准流程:trace 定位慢在哪一层,watch 看具体的参数和返回值。
  • profiler 火焰图提供 CPU 热点的全局视角,适合复杂调用链的性能分析。
  • redefine 热更新可用于紧急止血,但有风险且不持久化,生产环境慎用并需后续正式发布固化。
  • 安全第一:Arthas 等同于应用 root 权限,务必限制使用人员,排查后用reset+stop正确清理。

下一篇将系统梳理常见 JVM 参数配置,为生产环境的 JVM 调优提供参数级指南。

← 返回列表