在生产环境中,我们经常遇到这样的场景:接口突然变慢、偶发性异常、CPU飙升、内存泄漏等问题。传统的排查方式需要重新部署、添加日志,耗时费力。而 Arthas 作为阿里开源的 Java 诊断工具,可以在不重启应用的情况下,实时诊断线上问题。

本文将通过实战案例,带你掌握 Arthas 的核心功能,让生产问题排查事半功倍。


一、Arthas 快速入门

1.1 安装与启动

本地使用(可以正常联网的机器)

# 下载 arthas-boot.jar
curl -O https://arthas.aliyun.com/arthas-boot.jar

# 启动 Arthas
java -jar arthas-boot.jar

# 选择要诊断的 Java 进程,输入前面的数字 
[1] 12345 com.example.Application

启动后,Arthas 会自动 attach 到目标进程,无需修改任何代码或重启应用。

离线环境,不可联网

如果是不可联网的内网环境,可以通过 github https://github.com/alibaba/arthas/releases 下载完整包

#
wget https://github.com/alibaba/arthas/releases/download/arthas-all-4.1.2/arthas-bin.zip
unzip arthas-bin.zip -d arthas
cd arthas
#  linux、mac下使用 as.sh ,windows 下使用 as.bat
sh as.sh
# 选择要诊断的 Java 进程,输入前面的数字 
[1] 12345 com.example.Application

启动后,Arthas 会自动 attach 到目标进程,无需修改任何代码或重启应用。

1.2 基础命令速览

# 查看 JVM 信息
dashboard

# 查看线程信息
thread

# 查看已加载的类
sc com.example.*

# 查看方法信息
sm com.example.UserService

二、接口性能问题排查

2.1 场景:接口响应变慢

问题描述:用户反馈订单查询接口从 100ms 变慢到 3 秒以上。

使用 trace 命令追踪方法调用链

trace com.example.OrderService queryOrder

输出示例

分析结果:耗时集中在数据库查询,3200ms 占总耗时 99%。

深入追踪慢查询

# 追踪 SQL 执行
trace org.hibernate.query.Query getSingleResult -n 5

# 查看方法入参和返回值
watch com.example.OrderRepository findById '{params, returnObj}' -x 2

优化方向

  1. 检查是否缺少索引
  2. 是否存在 N+1 查询问题
  3. 考虑添加缓存

2.2 场景:找出最耗时的方法

使用 monitor 命令监控方法调用统计:

monitor -c 5 com.example.OrderService queryOrder

输出示例

timestamp      class.method              total  success  fail  avg-rt(ms)  fail-rate
2024-11-04     OrderService.queryOrder   156    148      8     2847.32     5.13%

分析

  • 失败率 5.13%,需要关注异常情况
  • 平均响应时间 2.8 秒,确认性能瓶颈

三、异常问题排查

3.1 场景:接口偶发性报错

问题描述:用户支付接口偶尔抛出 NullPointerException,但日志不完整。

使用 watch 捕获异常

watch com.example.PaymentService processPay '{params, throwExp}' -e -x 2

参数说明:

  • -e:只在方法抛异常时输出
  • -x 2:展开对象层级为 2 层

输出示例

method=com.example.PaymentService.processPay
params=[@PaymentRequest[orderId=12345, amount=null, userId=678]]
throwExp=java.lang.NullPointerException: amount cannot be null
    at com.example.PaymentService.processPay(PaymentService.java:45)

定位到问题:amount 字段为 null,需要在上游做参数校验。

3.2 查看异常发生时的完整上下文

# 捕获方法入参、返回值、异常及耗时
watch com.example.PaymentService processPay '{params, returnObj, throwExp, costMs}' -x 3 -n 5

3.3 追踪异常的根源

# 追踪调用链,找到异常最初来源
stack com.example.PaymentService processPay

输出

ts=2024-11-04 10:30:12;thread_name=http-nio-8080-exec-5;
    @com.example.PaymentController.pay()
        at com.example.PaymentService.processPay()
            at com.example.PaymentValidator.validate()
                at com.example.AmountChecker.check()

四、实时查看源码与反编译

4.1 场景:线上代码与预期不符

问题描述:明明修复了 bug 重新部署,但问题依然存在。

使用 jad 反编译查看实际代码

jad com.example.OrderService queryOrder

输出

public Order queryOrder(Long orderId) {
    // 检查实际运行的代码逻辑
    if (orderId == null) {
        return null;  //  😱 居然还是旧代码!新加的校验逻辑根本不在!
    }
    return orderRepository.findById(orderId);
}

发现问题:部署的 jar 包版本不对,或者类加载器加载了旧版本的类。

4.2 查看类的加载信息

# 查看类从哪个 jar 包加载
sc -d com.example.OrderService

# 输出示例
class-info:
  class: com.example.OrderService
  classLoaderHash: 2a3d9c5f
  codeSource: /app/libs/order-service-1.0.0.jar
  isInterface: false

4.3 对比不同版本的代码

# 反编译并保存到文件
jad --source-only com.example.OrderService > /tmp/OrderService_v1.java

# 部署新版本后再次对比
jad --source-only com.example.OrderService > /tmp/OrderService_v2.java

五、高级技巧:方法调用详细分析

5.1 查看方法的入参和返回值

# 查看所有参数和返回值
watch com.example.UserService findUser '{params, returnObj}' -x 3

# 条件过滤:只看 userId=1001 的调用
watch com.example.UserService findUser '{params, returnObj}' 'params[0]==1001' -x 3

5.2 统计方法调用成功率

# 每 5 秒统计一次
monitor -c 5 com.example.UserService findUser

5.3 获取方法调用时间分布

# 查看哪些调用超过 1 秒
trace com.example.UserService findUser '#cost > 1000'

六、生产环境最佳实践

6.1 性能影响最小化

# 限制监控次数,避免影响性能
trace com.example.OrderService queryOrder -n 10

# 设置条件过滤,减少输出
watch com.example.OrderService queryOrder '{params}' 'params[0] > 10000' -n 5

6.2 定位热点方法

# 找出 CPU 占用最高的线程
thread -n 3

# 查看具体线程的堆栈
thread 123

6.3 保存诊断结果

# 将输出重定向到文件
trace com.example.OrderService queryOrder > /tmp/trace_result.log

# 导出诊断报告
options json-format true
profiler start
profiler stop --format html --file /tmp/profile.html

七、常见问题与解决方案

7.1 Arthas 无法 attach 到进程

解决方案

# 使用 root 权限
sudo java -jar arthas-boot.jar

# 或指定 PID
java -jar arthas-boot.jar <PID>

7.2 命令执行后无输出

原因

  • 方法没有被调用到
  • 类名或方法名拼写错误

排查步骤

# 1. 确认类已加载
sc com.example.OrderService

# 2. 确认方法存在
sm com.example.OrderService

# 3. 使用模糊匹配
trace com.example.*Service query*

7.3 输出内容被截断

# 增加展开层级
watch com.example.OrderService queryOrder '{params}' -x 4

# 设置输出行数
options unsafe true

八、实战案例总结

案例 1:接口 RT 突增

问题:订单查询接口响应时间从 100ms 飙升到 5 秒

排查步骤

  1. 使用 trace 追踪调用链,发现耗时在 Redis 连接超时
  2. 使用 thread 查看线程堆栈,发现大量线程阻塞在 Redis 连接获取
  3. 检查 Redis 连接池配置,发现 maxTotal=5 太小
  4. 调整连接池大小,问题解决

案例 2:偶发 NPE

问题:支付回调接口偶尔抛出 NullPointerException

排查步骤

  1. 使用 watch -e 捕获异常时的参数
  2. 发现某个上游服务返回的字段为 null
  3. 添加非空校验,问题解决

案例 3:新版本上线后功能异常

问题:修复的 bug 在线上依然复现

排查步骤

  1. 使用 jad 反编译查看实际代码
  2. 发现运行的是旧版本代码
  3. 检查 CI/CD 流程,发现打包时引用了旧依赖
  4. 重新打包部署,问题解决

九、命令速查表

命令用途示例
trace追踪方法调用链路trace com.example.Service method
watch观察方法入参/返回值/异常watch com.example.Service method '{params, returnObj}'
monitor统计方法调用monitor -c 5 com.example.Service method
stack查看方法调用堆栈stack com.example.Service method
jad反编译类jad com.example.Service
sc查看类信息sc -d com.example.*
sm查看方法信息sm com.example.Service
thread查看线程信息thread -n 3

总结

Arthas 是 Java 开发者在生产环境排查问题的必备神器。通过本文介绍的场景和命令,你可以:

快速定位性能瓶颈:使用 trace 追踪方法耗时,精确到每一行代码
捕获异常根因:watch 命令实时捕获异常上下文,不放过任何细节
验证线上代码:jad 反编译确保运行的就是最新版本
零侵入式诊断:无需修改代码或重启服务,对业务影响降到最低

记住:工具用得好,下班下得早。赶紧在你的工具箱里装上 Arthas,让生产问题无处遁形!


关注公众号,获取更多 Java 性能优化与问题排查技巧!

参考资料:

Q.E.D.


寻门而入,破门而出