Arthas | Java 线上问题快速定位神器

什么是 Arthas

Arthas 是阿里巴巴开源的一款 Java 诊断工具,它能够帮助开发人员在线上环境中进行性能分析、故障排查和问题定位。Arthas 基于 JVM 工具接口(JVMTI)实现,可以在不修改应用代码的情况下,对运行中的 Java 应用进行诊断和监控。

简介 | arthas

Arthas 的核心功能

  1. 进程诊断:查看和管理 Java 进程
  2. 线程分析:查看线程状态、堆栈信息
  3. 内存分析:查看堆内存使用情况、对象分布
  4. 类加载:查看类加载信息、反编译类
  5. 方法执行:监控方法执行性能、参数和返回值
  6. 系统属性:查看和修改 JVM 系统属性
  7. 日志级别:动态修改日志级别

安装与启动

安装

bash 复制代码
# 下载 Arthas
curl -O https://arthas.aliyun.com/arthas-boot.jar

启动

bash 复制代码
# 启动 Arthas
java -jar arthas-boot.jar

启动后,Arthas 会列出当前运行的 Java 进程,选择需要诊断的进程编号即可进入交互模式。

线上常见问题排查流程

1. CPU 使用率过高问题排查

场景:应用响应缓慢,服务器 CPU 使用率持续超过 90%

排查步骤与具体命令

  1. 查看系统进程 CPU 使用情况

    bash 复制代码
    # Linux/Mac 系统
    top
    
    # Windows 系统
    tasklist | findstr java

    找到 CPU 使用率最高的 Java 进程 PID(例如 PID 为 1234)

  2. 进入该进程的 Arthas 控制台

    bash 复制代码
    java -jar arthas-boot.jar 1234
  3. 查看线程状态和堆栈信息

    bash 复制代码
    # 查看所有线程的 CPU 使用率
    thread -n 5 -cpu
    
    # 查看指定线程的详细堆栈(假设线程 ID 为 0x123)
    thread 0x123
  4. 定位 CPU 使用率高的线程

    thread -n 5 -cpu 输出中,找到 CPU 使用率最高的线程(例如线程 ID 为 0x123,CPU 使用率 85%)

  5. 分析线程堆栈,找出耗时操作

    查看该线程的堆栈信息,定位到具体的耗时方法

    bash 复制代码
    # 查看线程堆栈
    thread 0x123

    例如,发现 com.example.service.OrderService.calculatePrice() 方法占用大量 CPU 时间

示例输出与分析

bash 复制代码
$ thread -n 5 -cpu

Thread 0x123 (http-nio-8080-exec-1) CPU usage: 85%
Stack trace:
com.example.service.OrderService.calculatePrice(OrderService.java:123)
com.example.controller.OrderController.createOrder(OrderController.java:45)
...

分析:calculatePrice 方法可能存在复杂计算或循环,可以进一步使用 trace 命令分析该方法的执行链路

2. 内存泄漏问题排查

场景:应用运行一段时间后 OOM(OutOfMemoryError),堆内存持续增长

排查步骤与具体命令

  1. 查看内存使用情况

    bash 复制代码
    # 查看 JVM 内存使用实时数据
    dashboard
    
    # 查看堆内存详细信息
    jvm -heap

    关注 heap 部分的 usedmax 值,判断是否存在内存泄漏

  2. 导出堆内存快照

    bash 复制代码
    # 导出堆内存快照到指定文件
    heapdump /tmp/heapdump.hprof
  3. 分析堆内存快照

    bash 复制代码
    # 使用 jhat 分析(Java 自带工具)
    jhat /tmp/heapdump.hprof
    
    # 或者使用 Arthas 的 heapdump 命令结合其他工具如 MAT 分析

    访问 http://localhost:7000 查看 jhat 分析结果

  4. 定位内存泄漏的对象

    bash 复制代码
    # 查看对象实例数量和大小
    classloader -a | grep com.example
    
    # 查看指定类的实例数量
    sc -d com.example.model.User
    
    # 查看实例引用关系
    ognl '#obj=@com.example.model.User@getInstance(), #field=obj.getClass().getDeclaredField("cachedData"), #field.setAccessible(true), #field.get(obj)'
  5. 分析对象引用链,找出泄漏原因

    通过 jhat 或 MAT 工具查看对象的引用链,找出哪个对象持有大量实例的引用而不释放

示例输出与分析

bash 复制代码
$ sc -d com.example.model.User
 class-info        com.example.model.User
 code-source       /app/lib/user-service.jar
 name              com.example.model.User
 isInterface       false
 isAnnotation      false
 isEnum            false
 isAnonymousClass  false
 isArray           false
 isLocalClass      false
 isMemberClass     false
 loaded-by         sun.misc.Launcher$AppClassLoader@18b4aac2
 instance-count    1000000
 
$ jvm -heap
Heap Usage:
PS Young Generation
Eden Space:
   capacity = 1073741824 (1024.0MB)
   used     = 1073741824 (1024.0MB)
   free     = 0 (0.0MB)
   100.0% used
...

分析:User 类实例数量达到 100 万,且 Eden 空间已满,可能存在内存泄漏

3. 方法执行性能问题排查

场景:某个 API 接口响应时间过长,需要定位性能瓶颈

排查步骤与具体命令

  1. 跟踪方法执行链路

    bash 复制代码
    # 跟踪指定方法的执行链路
    trace com.example.controller.UserController getUserById
    
    # 跟踪方法并过滤掉 JDK 方法
    trace com.example.controller.UserController getUserById --skipJDKMethod
    
    # 设置采样时间(单位 ms)
    trace com.example.controller.UserController getUserById 1000
  2. 观察方法参数和返回值

    bash 复制代码
    # 观察方法的参数和返回值
    watch com.example.service.UserService getUserById '{params, returnObj}'
    
    # 观察方法执行时间超过 100ms 的调用
    watch com.example.service.UserService getUserById '{params, returnObj, costTime}' 'costTime > 100'
    
    # 观察方法异常情况
    watch com.example.service.UserService getUserById '{params, throwExp}' -e
  3. 统计方法执行耗时

    bash 复制代码
    # 统计方法执行耗时
    time com.example.service.UserService getUserById
    
    # 执行多次并计算平均耗时
    time -n 10 com.example.service.UserService getUserById
  4. 定位性能瓶颈方法

    tracetime 命令的输出中,找到执行时间最长的方法 例如,发现 com.example.dao.UserDao.queryById() 方法执行时间占比达 80%

  5. 优化方法实现

    根据定位结果进行优化,如添加缓存、优化 SQL 查询等

示例输出与分析

bash 复制代码
$ trace com.example.controller.UserController getUserById
---ts=2023-10-10 10:15:30;thread_name=http-nio-8080-exec-1;id=123;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@12345678
---[500ms] com.example.controller.UserController:getUserById()
+---[450ms] com.example.service.UserService:getUserById()
|   +---[400ms] com.example.dao.UserDao:queryById()
|   +---[50ms] com.example.service.CacheService:set()
+---[50ms] com.example.controller.UserController:buildResponse()

分析:UserDao.queryById() 方法占用了 400ms,是性能瓶颈,需要优化该方法

4. 死锁问题排查

场景:应用响应缓慢,线程无法正常执行,可能存在死锁

排查步骤与具体命令

  1. 查看线程状态

    bash 复制代码
    # 查看所有线程状态
    thread
    
    # 查看阻塞状态的线程
    thread -state BLOCKED
  2. 检测死锁

    bash 复制代码
    # 检测死锁
    thread -b

    如果存在死锁,会输出死锁的线程信息和持有的锁

  3. 分析死锁线程的堆栈信息

    bash 复制代码
    # 查看死锁线程的详细堆栈(假设线程 ID 为 0x123 和 0x456)
    thread 0x123
    thread 0x456

    分析线程持有的锁和等待的锁,找出死锁原因

  4. 找出死锁原因并解决

    根据堆栈信息,定位到代码中导致死锁的位置,修改代码以避免死锁

示例输出与分析

bash 复制代码
$ thread -b
Found one deadlock:

Java stack information for the threads listed above:
==================================================
"Thread-1":
  waiting to lock monitor 0x00007f1234567890 (object 0x0000000765432100, a java.lang.Object),
  which is held by "Thread-2"
"Thread-2":
  waiting to lock monitor 0x00007f1234567891 (object 0x0000000765432101, a java.lang.Object),
  which is held by "Thread-1"

Found 1 deadlock.

分析:Thread-1 持有对象 A 的锁,等待对象 B 的锁;Thread-2 持有对象 B 的锁,等待对象 A 的锁,形成死锁

5. 类加载冲突问题排查

场景 :应用启动时出现 NoClassDefFoundErrorClassNotFoundException,或运行时出现类版本不匹配错误

排查步骤与具体命令

  1. 查看类加载器信息

    bash 复制代码
    # 查看所有类加载器
    classloader
    
    # 查看指定类加载器加载的类
    classloader -c 18b4aac2

    其中 18b4aac2 是类加载器的 hashcode

  2. 查看类加载情况

    bash 复制代码
    # 查看类的加载信息
    sc -d com.example.service.UserService
    
    # 查看类的版本信息
    sc -d -f com.example.service.UserService
  3. 反编译类,检查类版本

    bash 复制代码
    # 反编译类
    jad com.example.service.UserService
    
    # 查看类的字节码
    dump com.example.service.UserService

    比较反编译后的代码与预期是否一致,检查类版本是否匹配

  4. 找出冲突的类并解决

    bash 复制代码
    # 查找类在哪些 JAR 包中存在
    classloader -l com.example.service.UserService

    根据查找结果,移除冲突的 JAR 包或调整类加载顺序

示例输出与分析

bash 复制代码
$ sc -d com.example.service.UserService
 class-info        com.example.service.UserService
 code-source       /app/lib/user-service-1.0.jar
 name              com.example.service.UserService
 isInterface       false
 isAnnotation      false
 isEnum            false
 isAnonymousClass  false
 isArray           false
 isLocalClass      false
 isMemberClass     false
 loaded-by         sun.misc.Launcher$AppClassLoader@18b4aac2

$ classloader -l com.example.service.UserService
Jar paths that contain class com.example.service.UserService:
/app/lib/user-service-1.0.jar
/app/lib/old-user-service-0.9.jar

分析:发现 UserService 类在两个 JAR 包中存在,可能导致类加载冲突,需要移除旧版本的 JAR 包

最佳实践

  1. 生产环境使用限制:避免在高并发生产环境长时间运行 Arthas,可能会影响应用性能
  2. 权限控制:限制 Arthas 访问权限,避免敏感信息泄露
  3. 命令使用规范:遵循公司安全规范,不执行危险命令
  4. 诊断数据保存:重要诊断数据及时保存,便于后续分析
  5. 版本更新:定期更新 Arthas 版本,获取最新功能和 Bug 修复
  6. 避免过度诊断:针对具体问题选择合适的命令,避免全面扫描
  7. 诊断会话管理:及时终止不需要的诊断会话,释放资源

常用命令速查

  • dashboard:查看系统实时数据面板
  • thread:查看线程状态和堆栈
  • jvm:查看 JVM 信息
  • sysprop:查看和修改系统属性
  • logger:查看和修改日志级别
  • classloader:查看类加载器信息
  • jad:反编译类
  • watch:观察方法执行
  • trace:跟踪方法调用链路
  • time:统计方法执行耗时
  • sc:查看类加载情况
  • sm:查看方法信息
  • heapdump:导出堆内存快照
  • ognl:执行 OGNL 表达式

进阶使用

自定义命令

Arthas 支持通过 OGNL 表达式自定义命令,实现更灵活的诊断需求。例如:

bash 复制代码
# 查看所有线程的状态
thread -state RUNNABLE

# 观察方法执行异常
watch com.example.service.UserService getUserById '{params, throwExp}' -e

# 跟踪方法调用链,过滤掉 jdk 方法
trace com.example.service.UserService getUserById --skipJDKMethod
trace com.example.service.UserService getUserById 1000 # 设置采样时间,单位 ms

条件表达式

可以使用条件表达式过滤命令结果:

bash 复制代码
# 查看 CPU 使用率超过 20% 的线程
thread -n 5 -cpu 20

# 观察参数 id 大于 100 的方法调用
watch com.example.service.UserService getUserById '{params, returnObj}' 'params[0] > 100'

集成到应用

可以将 Arthas 集成到 Spring Boot 应用中,通过 HTTP 接口访问 Arthas 功能。具体集成方式如下:

  1. 添加依赖
xml 复制代码
<dependency>
    <groupId>com.taobao.arthas</groupId>
    <artifactId>arthas-spring-boot-starter</artifactId>
    <version>3.6.7</version>
</dependency>
  1. 配置应用属性
yaml 复制代码
arthas:
  agent-id: ${spring.application.name}-${server.port}
  tunnel-server: ws://arthas-server:7777/ws
  1. 启动应用后,通过 Arthas Tunnel Server 访问

编写 Arthas 脚本

可以将常用的诊断命令编写成脚本,方便重复执行:

bash 复制代码
# 保存为 cpu-high-diagnosis.as
# 查看 CPU 使用率最高的 5 个线程
thread -n 5
# 查看 JVM 内存使用情况
jvm
# 查看系统属性
sysprop

执行脚本:

bash 复制代码
$ as.sh -f cpu-high-diagnosis.as

总结

Arthas 是一款强大的 Java 诊断工具,能够帮助我们快速定位和解决线上问题。掌握 Arthas 的核心功能和最佳实践,可以显著提高线上问题排查效率,保障应用稳定运行。无论是 CPU 使用率过高、内存泄漏、方法执行性能问题,还是死锁、类加载冲突等问题,Arthas 都能提供有效的解决方案。

相关推荐
青云交几秒前
Java 大视界 -- 基于 Java 的大数据实时流处理在工业物联网设备故障预测与智能运维中的应用(384)
java·大数据·物联网·flink·设备故障预测·智能运维·实时流处理
茗创科技2 分钟前
Nature Neuroscience | 如何在大规模自动化MRI分析中规避伪影陷阱?
运维·自动化
半桔11 分钟前
【STL源码剖析】从源码看 vector:底层扩容逻辑与内存复用机制
java·开发语言·c++·容器·stl
洛卡卡了22 分钟前
面试官问限流降级,我项目根本没做过,咋办?
后端·面试·架构
慕y27423 分钟前
Java学习第一百零九部分——Jenkins(一)
java·学习·jenkins
ezl1fe41 分钟前
RAG 每日一技(十四):化繁为简,统揽全局——用LangChain构建高级RAG流程
人工智能·后端·算法
悟能不能悟1 小时前
cdn是什么
java
amazingCompass1 小时前
Java 开发必备技能:深入理解与实战 IntelliJ IDEA 中的 VM Options
后端
爱科研的瞌睡虫1 小时前
C++线程中 detach() 和 join() 的区别
java·c++·算法
每天的每一天1 小时前
分布式文件系统05-生产级中间件的Java网络通信技术深度优化
java·开发语言·中间件