Arthas 线上问题诊断实战
阿里巴巴开源 Java 诊断工具,线上问题排查利器
一、为什么需要 Arthas
在分布式系统开发中,我们经常遇到这样的困境:
开发环境正常,测试环境正常,线上环境出问题
日志看不到关键信息,重启后问题无法复现
服务器 CPU 飙升,内存泄漏,接口响应慢传统的排查方式存在诸多限制:
| 排查方式 | 局限性 |
|---|---|
| 日志排查 | 需要预先埋点,问题发生前可能没记录 |
| 调试 debug | 无法在生产环境使用 |
| JVisualVM | 需要打开端口,配置复杂 |
| 重新部署 | 问题可能无法复现,治标不治本 |
Arthas 的核心优势:
1. 无侵入:无需修改代码,无需重启服务
2. 功能强大:支持反编译、方法追踪、动态代理
3. 实时性:在线诊断,所见即所得
4. 跨语言:支持 Java、Scala 等 JVM 语言二、安装与启动
2.1 快速启动
Arthas 支持多种启动方式:
bash
# 方式一:curl 快速启动(推荐)
curl -L https://arthas.aliyun.com/install.sh | sh
# 启动 Arthas
java -jar arthas-boot.jar
# 方式二:直接下载完整包
wget https://arthas.aliyun.com/arthas-boot.jar
# 启动
java -jar arthas-boot.jar2.2 .attach 到目标进程
启动后会显示当前 JVM 进程列表:
[INFO] arthas home: /root/.arthas/lib/3.7.2
[INFO] Try to attach process 12345
[INFO] Attach success.
[INFO] arthas client connect to arthas server successfully.
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki https://arthas.aliyun.com/doc
tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html
version 3.7.2
main_class com.example.Application
pid 12345
time 2024-01-15 10:30:002.3 Web Console 方式
通过浏览器访问:
bash
# 启动时指定端口
java -jar arthas-boot.jar --telnet-port 3658 --http-port 8563
# 访问 Web 控制台
# http://localhost:8563三、常用命令详解
3.1 dashboard - 实时监控面板
查看 JVM 整体运行状态:
bash
# 进入 Arthas 后直接执行
dashboard
# 输出效果:
# 全局看板:
# PID CPU(%) RSS(M) VSZ(M) 线程数
# 12345 85.2 512.0 1024.0 45
# 线程信息:
# ID CPU(%) STATE NAME
# 1 0.1 RUNNABLE main
# 12 85.0 RUNNING http-nio-8080-ClientPoller
# 15 2.3 WAITING C2 CompilerThread0
# 内存信息:
# HEAP used total max usage
# eden 256M 512M 512M 50.0%
# old 128M 256M 512M 25.0%
# GC 统计:
# young gc 156 0.5s
# full gc 3 2.3sdashboard 包含四个核心面板:
| 面板 | 显示内容 | 用途 |
|---|---|---|
| JVM 概览 | CPU、内存、负载 | 快速定位资源瓶颈 |
| 线程状态 | RUNNING、WAITING、阻塞 | 排查线程问题 |
| 内存分布 | Eden、Survivor、Old | 内存泄漏分析 |
| GC 统计 | 频率、耗时 | GC 调优参考 |
3.2 thread - 线程分析
查看线程状态和 CPU 占用:
bash
# 查看所有线程
thread
# 查看 CPU 占用最高的线程
thread -n 5
# 查看指定线程的堆栈
thread 15
# 查找线程阻塞的代码位置
thread -b
# 示例输出:
"http-nio-8080-ClientPoller" Id=12 cpuUsage=85.0%
java.base@17.0.2/sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
java.base@17.0.2/sun.nio.ch.EPollArrayWrapper.epollWait(EPollArrayWrapper.java:509)
java.base@17.0.2/sun.nio.ch.EPollArrayWrapper.interrupt(EPollArrayWrapper.java:288)
java.base@17.0.2/sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:246)
java.base@17.0.2/sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:195)
org.apache.nio.core.NioChannelWrapper.read(NioChannelWrapper.java:89)
com.example.handler.WebSocketHandler.onMessage(WebSocketHandler.java:45)3.3 jad - 反编译 class
在线查看源码,无需依赖源码:
bash
# 反编译指定类
jad com.example.service.OrderService
# 反编译并显示源码行号
jad --source-only com.example.service.OrderService
# 反编译指定方法
jad com.example.service.OrderService calculatePrice
# 示例输出:
/*
* Decompiled with CFR.
*/
package com.example.service;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
public double calculatePrice(Long orderId) {
Order order = this.orderMapper.selectById(orderId);
if (order == null) {
throw new RuntimeException("订单不存在");
}
return order.getAmount() * order.getDiscount();
}
}3.4 watch - 方法调用监控
观察方法执行的入参和返回值:
bash
# 监控方法调用
watch com.example.service.OrderService calculatePrice "{params, returnObj}"
# 只看入参
watch com.example.service.OrderService calculatePrice "params"
# 只看返回值
watch com.example.service.OrderService calculatePrice "returnObj"
# 监控异常信息
watch com.example.service.OrderService calculatePrice "{params, returnObj, throwExp}"
# 指定监控条件(条件表达式)
watch com.example.service.OrderService calculatePrice "{params, returnObj}" "params[0] > 1000"
# 监听方法执行后,返回值和耗时
watch com.example.service.OrderService calculatePrice "{params, returnObj, cost}" -x 3
# 参数说明:
# -x 3:展开深度,默认1层
# 条件表达式:params[0] > 1000 只监控订单ID大于1000的调用watch 命令常用场景:
| 场景 | 命令示例 |
|---|---|
| 监控关键参数 | watch com.example.Controller * "{params}" |
| 监控异常 | watch com.example.Service * "{throwExp}" |
| 性能分析 | watch com.example.Service * "{params, returnObj, cost}" |
| 条件过滤 | watch com.example.Service method "params[0] > 100" |
3.5 trace - 方法调用链路追踪
追踪方法调用的完整链路:
bash
# 追踪方法调用耗时
trace com.example.service.OrderService calculatePrice
# 只显示耗时超过 100ms 的调用
trace com.example.service.OrderService calculatePrice "#cost > 100"
# 追踪到深层方法
trace com.example.service.OrderService *
# 追踪结果示例:
`---ts=2024-01-15 10:30:00;time_cost=245ms [1080b] --- execution path: A
`---[0.123ms] com.example.service.OrderService:123
`---[0.045ms] com.example.mapper.OrderMapper:45
`---[0.032ms] getOrderById() // 耗时最短
`---[0.015ms] SQL: SELECT * FROM order WHERE id = ?
`---[0.056ms] com.example.service.DiscountService:67
`---[0.020ms] getDiscount()
`---[0.030ms] calculateFinalPrice()
`---[0.045ms] com.example.service.PromotionService:89
`---[0.025ms] checkPromotion()
`---[0.018ms] applyPromotion()3.6 stack - 方法调用栈追踪
查看方法是被谁调用的:
bash
# 查看方法调用栈
stack com.example.service.OrderService calculatePrice
# 指定条件
stack com.example.service.OrderService calculatePrice "#cost > 50"
# 输出示例:
stack com.example.service.OrderService calculatePrice
ts=2024-01-15 10:30:00
@com.example.controller.OrderController.queryOrder()
params: [1001]
location: com.example.controller.OrderController:45
@com.example.controller.OrderController.createOrder()
params: [1002]
location: com.example.controller.OrderController:783.7 tt - 时间隧道(记录方法调用)
多次调用方法,查看历史调用记录:
bash
# 记录方法调用
tt -t com.example.service.OrderService calculatePrice
# 停止记录
tt -t
# 查看历史调用列表
tt -l
# 查看指定调用的详细信息
tt -i 1001
# 重新执行指定调用
tt -p -i 1001
# tt 常用选项:
# -t:开始记录方法调用
# -l:列出所有记录的调用
# -i:查看指定调用的详情
# -p:重新执行指定调用(重放)
# -x:展开深度
# 示例:
tt -t com.example.service.OrderService calculatePrice -n 50
# 输出:
# INDEX TIMESTAMP COST(ms) OBJECT CLASS
# 1001 2024-01-15 10:30:00 45 NULL OrderService
# 1002 2024-01-15 10:30:01 32 NULL OrderService
# 1003 2024-01-15 10:30:02 156 NULL OrderService3.8 heapdump - 堆内存导出
导出堆内存快照,分析内存泄漏:
bash
# 导出堆内存
heapdump
# 导出到指定文件
heapdump /tmp/dump.hprof
# 只导出活跃对象
heapdump --live /tmp/live-dump.hprof
# 导出后使用 MAT (Memory Analyzer Tool) 分析3.9 ognl - 动态表达式执行
执行任意 Java 表达式,获取运行时信息:
bash
# 获取静态变量
ognl '@com.example.config.Config@ENABLE_LOGIN'
# 调用静态方法
ognl '@java.lang.System@getProperty("java.version")'
# 获取 Spring Bean
ognl '@springContext.getBean("userService")'
# 修改线上配置(谨慎使用)
ognl '@com.example.config.Config@setEnableLogin(false)'
# 查看当前所有 System Properties
ognl '#{sysProps = @java.lang.System@getProperties(), #sysProps}'
# ognl 常用场景:
# - 动态修改配置
# - 获取运行时状态
# - 调用任意方法四、实战场景
4.1 场景一:接口响应慢排查
问题描述:用户反馈订单接口响应很慢,经常超时。
bash
# 1. 先用 dashboard 查看整体情况
dashboard
# 2. 发现某个线程 CPU 占用很高
thread -n 5
# 3. 追踪慢接口
trace com.example.service.OrderService calculatePrice "#cost > 500"
# 4. 定位到是数据库查询慢
watch com.example.mapper.OrderMapper "params, returnObj" "#cost > 100"
# 5. 查看具体 SQL
stack com.example.mapper.OrderMapper.getOrderById
# 诊断结果:发现缺少索引,导致全表扫描
# 解决方案:添加数据库索引4.2 场景二:内存泄漏排查
问题描述:服务运行几天后内存持续增长,最终 OOM。
bash
# 1. 查看内存分布
memory
# 2. 观察 Old 区持续增长
dashboard
# 3. 手动触发 Full GC
gc
# 4. 导出堆内存分析
heapdump /tmp/heap.hprof
# 5. 或者在线查看对象数量
ognl '{#map = new java.util.HashMap(), #map}'
sm java.lang.String '<init>'
# 使用 MAT 分析 dump 文件:
# 1. 打开 MAT
# 2. Leak Suspects Report
# 3. 查看占用内存最大的对象
# 4. 定位泄漏代码4.3 场景三:线上代码热更新
问题描述:发现线上代码有 bug,需要紧急修复。
bash
# 1. 先反编译确认当前代码
jad com.example.service.OrderService
# 2. 查看需要修改的方法
jad com.example.service.OrderService calculatePrice
# 3. 使用 mc (memory compiler) 编译新代码
mc /path/to/OrderService.java -d /tmp/
# 4. 使用 redefine 热更新
redefine /tmp/com/example/service/OrderService.class
# 注意:
# - 不支持修改类结构(方法数、字段数)
# - 不支持修改方法签名
# - 多次 redefine 会有限制
# 更推荐的做法:
# 1. 使用 watch 确认问题
# 2. 在测试环境修复并测试
# 3. 重新打包发布4.4 场景四:监听参数定位问题
问题描述:订单金额计算异常,需要查看入参和返回值。
bash
# 监听订单计算方法
watch com.example.service.OrderService calculatePrice \
"{params, returnObj, throwExp}" \
-x 2 \
-n 10
# 输出:
# ts=2024-01-15 10:30:00
# params=[1001, 0.8]
# returnObj=799.2
# throwExp=NULL
#
# ts=2024-01-15 10:30:01
# params=[1002, 0.7]
# returnObj=699.3
# throwExp=NULL
# 监听异常
watch com.example.service.OrderService calculatePrice \
"{params, throwExp}" \
"#throwExp != null" \
-n 54.5 场景五:追踪方法调用链路
问题描述:想知道订单接口被哪些地方调用。
bash
# 追踪所有调用链路
trace com.example.service.OrderService * -n 10
# 追踪到 Controller 层
stack com.example.controller.OrderController * "#methodName.contains('order')"
# 查看最慢的调用
trace *Service * "#cost > 200"五、进阶技巧
5.1 批处理脚本
将常用命令保存为脚本:
bash
# 文件:order-service.sh
#!/bin/bash
echo "========== 订单服务诊断 =========="
# 查看 CPU 占用
echo "\n【线程 CPU 占用 Top 5】"
thread -n 5
# 追踪慢接口
echo "\n【慢接口追踪】"
trace com.example.service.OrderService * "#cost > 100"
# 监听关键方法
echo "\n【方法调用监控】"
watch com.example.service.OrderService calculatePrice \
"{params, returnObj}" \
"#params[0] > 1000" \
-x 1 \
-n 20
# 执行脚本
./order-service.sh > diagnosis-report.txt5.2 火焰图(Flame Graph)
生成性能火焰图:
bash
# 1. 开启火焰图采集
profiler start
# 2. 执行待测操作
# 3. 停止采集
profiler stop
# 4. 生成火焰图
profiler svg
# 或者指定采集时间
profiler start --duration 60
profiler stop
# 查看火焰图
# 火焰图展示了调用栈的深度和每个方法的 CPU 占用
# 宽度越大表示占用 CPU 越多
# 顶部是入口方法,底部是底层方法5.3 异步任务
Arthas 支持在后台执行任务:
bash
# 每 5 秒输出一次 thread 信息,共执行 10 次
thread -n 5 -i 5000 -c 10 > thread-monitor.log &
# 监控方法调用,5 秒后自动退出
watch com.example.Service method "{params}" -x 2 -w 5
# 后台任务管理
jobs # 查看后台任务
kill 1 # 停止后台任务5.4 权限控制
生产环境建议开启认证:
bash
# 启动 Arthas 时启用密码认证
java -jar arthas-boot.jar \
--password arthas123 \
--username admin
# 禁用某些危险命令
# 在 arthas.properties 中配置
disabled_commands=ognl,heapdump,redefine六、最佳实践
6.1 常用命令速查表
| 命令 | 用途 | 场景 |
|---|---|---|
| dashboard | 整体监控 | 快速了解 JVM 状态 |
| thread | 线程分析 | CPU 高、线程阻塞 |
| trace | 调用链路 | 定位慢接口 |
| watch | 方法监控 | 查看参数和返回值 |
| tt | 时间隧道 | 记录和回放调用 |
| jad | 反编译 | 查看当前代码 |
| stack | 调用栈 | 追踪方法调用者 |
| ognl | 动态执行 | 获取/修改运行时状态 |
| heapdump | 内存导出 | 内存泄漏分析 |
6.2 问题排查思路
遇到问题时的排查顺序:
1. 先看整体:dashboard
- CPU 是否异常
- 内存是否增长
- GC 是否频繁
2. 再看线程:thread
- 是否有线程 CPU 很高
- 是否有线程阻塞
- 线程数是否过多
3. 最后追踪:trace/watch
- 定位具体方法
- 查看参数和返回值
- 追踪调用链路
4. 如需深度分析
- heapdump + MAT
- 火焰图分析6.3 注意事项
bash
# 1. 谨慎使用 ognl 修改配置
# 修改可能影响线上业务
# 2. trace 和 watch 会对性能有一定影响
# 生产环境尽量减少使用
# 3. heapdump 会暂停 JVM
# 大内存服务慎用,建议使用 --live
# 4. 热更新有限制
# 推荐用于紧急修复小问题
# 5. 建议在测试环境先验证命令
# 避免对生产环境造成影响七、总结
Arthas 是 Java 开发者的必备工具,它让线上问题排查变得简单高效:
1. 上手简单:curl 即可安装,交互式命令行
2. 功能强大:覆盖诊断、监控、追踪、分析等场景
3. 无侵入:无需修改代码,所见即所得
4. 生产环境友好:支持认证、限流等安全措施
熟练掌握 Arthas,可以快速定位和解决:
- 接口响应慢
- CPU 飙升
- 内存泄漏
- 线程阻塞
- 代码逻辑问题建议将常用命令保存为脚本,遇到问题时快速执行,提高排查效率。