Skip to content

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.jar

2.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:00

2.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.3s

dashboard 包含四个核心面板:

面板显示内容用途
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:78

3.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     OrderService

3.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 5

4.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.txt

5.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 飙升
- 内存泄漏
- 线程阻塞
- 代码逻辑问题

建议将常用命令保存为脚本,遇到问题时快速执行,提高排查效率。