
什么是 Arthas
Arthas 是阿里巴巴开源的一款 Java 诊断工具,它能够帮助开发人员在线上环境中进行性能分析、故障排查和问题定位。Arthas 基于 JVM 工具接口(JVMTI)实现,可以在不修改应用代码的情况下,对运行中的 Java 应用进行诊断和监控。
Arthas 的核心功能
- 进程诊断:查看和管理 Java 进程
- 线程分析:查看线程状态、堆栈信息
- 内存分析:查看堆内存使用情况、对象分布
- 类加载:查看类加载信息、反编译类
- 方法执行:监控方法执行性能、参数和返回值
- 系统属性:查看和修改 JVM 系统属性
- 日志级别:动态修改日志级别
安装与启动
安装
bash
# 下载 Arthas
curl -O https://arthas.aliyun.com/arthas-boot.jar
启动
bash
# 启动 Arthas
java -jar arthas-boot.jar
启动后,Arthas 会列出当前运行的 Java 进程,选择需要诊断的进程编号即可进入交互模式。
线上常见问题排查流程
1. CPU 使用率过高问题排查
场景:应用响应缓慢,服务器 CPU 使用率持续超过 90%
排查步骤与具体命令:
-
查看系统进程 CPU 使用情况
bash# Linux/Mac 系统 top # Windows 系统 tasklist | findstr java
找到 CPU 使用率最高的 Java 进程 PID(例如 PID 为 1234)
-
进入该进程的 Arthas 控制台
bashjava -jar arthas-boot.jar 1234
-
查看线程状态和堆栈信息
bash# 查看所有线程的 CPU 使用率 thread -n 5 -cpu # 查看指定线程的详细堆栈(假设线程 ID 为 0x123) thread 0x123
-
定位 CPU 使用率高的线程
从
thread -n 5 -cpu
输出中,找到 CPU 使用率最高的线程(例如线程 ID 为 0x123,CPU 使用率 85%) -
分析线程堆栈,找出耗时操作
查看该线程的堆栈信息,定位到具体的耗时方法
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),堆内存持续增长
排查步骤与具体命令:
-
查看内存使用情况
bash# 查看 JVM 内存使用实时数据 dashboard # 查看堆内存详细信息 jvm -heap
关注
heap
部分的used
和max
值,判断是否存在内存泄漏 -
导出堆内存快照
bash# 导出堆内存快照到指定文件 heapdump /tmp/heapdump.hprof
-
分析堆内存快照
bash# 使用 jhat 分析(Java 自带工具) jhat /tmp/heapdump.hprof # 或者使用 Arthas 的 heapdump 命令结合其他工具如 MAT 分析
访问 http://localhost:7000 查看 jhat 分析结果
-
定位内存泄漏的对象
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)'
-
分析对象引用链,找出泄漏原因
通过 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 接口响应时间过长,需要定位性能瓶颈
排查步骤与具体命令:
-
跟踪方法执行链路
bash# 跟踪指定方法的执行链路 trace com.example.controller.UserController getUserById # 跟踪方法并过滤掉 JDK 方法 trace com.example.controller.UserController getUserById --skipJDKMethod # 设置采样时间(单位 ms) trace com.example.controller.UserController getUserById 1000
-
观察方法参数和返回值
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
-
统计方法执行耗时
bash# 统计方法执行耗时 time com.example.service.UserService getUserById # 执行多次并计算平均耗时 time -n 10 com.example.service.UserService getUserById
-
定位性能瓶颈方法
从
trace
和time
命令的输出中,找到执行时间最长的方法 例如,发现com.example.dao.UserDao.queryById()
方法执行时间占比达 80% -
优化方法实现
根据定位结果进行优化,如添加缓存、优化 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. 死锁问题排查
场景:应用响应缓慢,线程无法正常执行,可能存在死锁
排查步骤与具体命令:
-
查看线程状态
bash# 查看所有线程状态 thread # 查看阻塞状态的线程 thread -state BLOCKED
-
检测死锁
bash# 检测死锁 thread -b
如果存在死锁,会输出死锁的线程信息和持有的锁
-
分析死锁线程的堆栈信息
bash# 查看死锁线程的详细堆栈(假设线程 ID 为 0x123 和 0x456) thread 0x123 thread 0x456
分析线程持有的锁和等待的锁,找出死锁原因
-
找出死锁原因并解决
根据堆栈信息,定位到代码中导致死锁的位置,修改代码以避免死锁
示例输出与分析:
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. 类加载冲突问题排查
场景 :应用启动时出现 NoClassDefFoundError
或 ClassNotFoundException
,或运行时出现类版本不匹配错误
排查步骤与具体命令:
-
查看类加载器信息
bash# 查看所有类加载器 classloader # 查看指定类加载器加载的类 classloader -c 18b4aac2
其中
18b4aac2
是类加载器的 hashcode -
查看类加载情况
bash# 查看类的加载信息 sc -d com.example.service.UserService # 查看类的版本信息 sc -d -f com.example.service.UserService
-
反编译类,检查类版本
bash# 反编译类 jad com.example.service.UserService # 查看类的字节码 dump com.example.service.UserService
比较反编译后的代码与预期是否一致,检查类版本是否匹配
-
找出冲突的类并解决
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 包
最佳实践
- 生产环境使用限制:避免在高并发生产环境长时间运行 Arthas,可能会影响应用性能
- 权限控制:限制 Arthas 访问权限,避免敏感信息泄露
- 命令使用规范:遵循公司安全规范,不执行危险命令
- 诊断数据保存:重要诊断数据及时保存,便于后续分析
- 版本更新:定期更新 Arthas 版本,获取最新功能和 Bug 修复
- 避免过度诊断:针对具体问题选择合适的命令,避免全面扫描
- 诊断会话管理:及时终止不需要的诊断会话,释放资源
常用命令速查
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 功能。具体集成方式如下:
- 添加依赖
xml
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-spring-boot-starter</artifactId>
<version>3.6.7</version>
</dependency>
- 配置应用属性
yaml
arthas:
agent-id: ${spring.application.name}-${server.port}
tunnel-server: ws://arthas-server:7777/ws
- 启动应用后,通过 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 都能提供有效的解决方案。