一、Arthas 是什么
1.1 一句话概括
Arthas 是阿里开源的 Java 应用诊断利器,让你无需重启应用、无需修改代码,就能实时诊断线上问题。
1.2 解决的痛点
传统问题排查的困境:
线上问题 → 加日志 → 重新打包 → 发布重启 → 等待复现 → 分析日志
↓
整个过程耗时长,影响业务
使用 Arthas 的优势:
线上问题 → 启动 Arthas → 实时诊断 → 立即定位问题
↓
无需重启,零侵入
1.3 核心特性
| 特性 | 说明 | 价值 |
|---|---|---|
| 零侵入 | 不需要修改代码或配置 | 不影响现有业务 |
| 实时性 | 实时查看方法调用、参数、返回值 | 快速定位问题 |
| 热更新 | 动态修改类、热更新代码 | 紧急修复线上 Bug |
| 性能分析 | CPU、内存、线程分析 | 性能调优利器 |
| 易用性 | 命令行友好,自动补全 | 学习成本低 |
二、核心作用与应用场景
2.1 六大核心能力
┌─────────────────────────────────────────────────────────┐
│ Arthas 核心能力 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. 类加载诊断 ←→ 查看类加载信息、类冲突 │
│ 2. 方法追踪 ←→ 监控方法调用、参数、返回值 │
│ 3. 性能分析 ←→ CPU、内存、线程分析 │
│ 4. 热更新 ←→ 动态修改代码、紧急修复 │
│ 5. 反编译 ←→ 查看线上实际运行的代码 │
│ 6. 监控告警 ←→ 实时监控方法执行情况 │
│ │
└─────────────────────────────────────────────────────────┘
2.2 典型应用场景
场景 1:线上问题排查
java
// 问题:用户反馈接口响应慢,但日志不足
// 传统方式:加日志 → 重启 → 等待复现
// Arthas 方式:直接监控方法执行
watch com.example.UserService getUserInfo '{params, returnObj, throwExp}' -x 3
场景 2:内存泄漏分析
bash
# 查看堆内存占用最多的对象
dashboard # 实时监控 JVM 状态
heapdump /tmp/dump.hprof # 导出堆转储文件
场景 3:CPU 飙高排查
bash
# 查看 CPU 占用最高的线程
thread -n 3 # 查看 CPU 占用前 3 的线程
thread <线程ID> # 查看线程详细堆栈
场景 4:类冲突问题
bash
# 多个 jar 包包含同名类,加载了哪个?
sc -d com.example.User # 查看类详细信息
jad com.example.User # 反编译查看实际代码
场景 5:热修复生产 Bug
bash
# 紧急修复线上 Bug,无需重启
jad --source-only com.example.BugClass > /tmp/BugClass.java
# 修改代码
mc /tmp/BugClass.java -d /tmp # 编译
redefine /tmp/BugClass.class # 热更新
三、快速上手
3.1 安装与启动
方式一:快速启动(推荐)
bash
# 下载并启动
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
方式二:完整安装
bash
# 下载完整包
wget https://arthas.aliyun.com/arthas-packaging-latest.zip
unzip arthas-packaging-latest.zip
cd arthas
./as.sh
方式三:在线一键安装
bash
curl -L https://arthas.aliyun.com/install.sh | sh
3.2 连接 Java 进程
bash
# 启动后会列出所有 Java 进程
$ java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.7.1
[INFO] Found existing java process, please choose one and hit RETURN.
* [1]: 12345 com.example.Application
[2]: 67890 org.apache.catalina.startup.Bootstrap
[3]: 11111 org.elasticsearch.bootstrap.Elasticsearch
# 输入序号选择进程
1
[INFO] arthas home: /root/.arthas/lib/3.7.1/arthas
[INFO] Try to attach process 12345
[INFO] Attach process 12345 success.
3.3 基础命令体验
bash
# 1. 查看 JVM 实时状态(类似 Linux top)
dashboard
# 2. 查看线程信息
thread
# 3. 查看类信息
sc *UserService
# 4. 反编译类
jad com.example.UserService
# 5. 监控方法调用
watch com.example.UserService getUserInfo '{params, returnObj}' -x 2
# 6. 退出
quit # 退出当前 Arthas 客户端(不影响已 attach 的进程)
stop # 完全停止 Arthas(detach 所有进程)
四、核心功能详解
4.1 类相关命令
4.1.1 sc - 查看类信息(Search Class)
bash
# 1. 基础用法:模糊搜索类
sc *UserService
# 输出:
# com.example.service.UserService
# com.example.admin.UserService
# 2. 查看类详细信息(-d: detail)
sc -d com.example.service.UserService
# 输出:
# class-info com.example.service.UserService
# code-source /app/lib/user-service.jar
# name com.example.service.UserService
# isInterface false
# isAnnotation false
# isEnum false
# isAnonymousClass false
# classLoaderHash 3f99bd52
核心参数:
-d:显示详细信息(类加载器、jar 位置等)-f:显示类的字段信息-E:开启正则表达式匹配
4.1.2 sm - 查看方法信息(Search Method)
bash
# 1. 查看类的所有方法
sm com.example.service.UserService
# 2. 查看特定方法详情
sm -d com.example.service.UserService getUserInfo
# 输出:
# declaring-class com.example.service.UserService
# method-name getUserInfo
# modifier public
# annotation
# parameters java.lang.String
# return com.example.dto.UserDTO
# exceptions
4.1.3 jad - 反编译(Java Decompiler)
bash
# 1. 反编译整个类
jad com.example.service.UserService
# 2. 只反编译某个方法
jad com.example.service.UserService getUserInfo
# 3. 输出源代码到文件
jad --source-only com.example.service.UserService > /tmp/UserService.java
典型场景:
bash
# 场景:怀疑线上代码与预期不符
jad com.example.OrderService createOrder
# 输出的反编译代码可以确认:
# 1. 是否包含最新的修改
# 2. 是否使用了正确的依赖版本
# 3. 是否有意外的代码逻辑
4.2 方法监控命令
4.2.1 watch - 观察方法调用(最常用)
bash
# 基础语法
watch <类名> <方法名> <观察表达式> [条件表达式]
# 1. 观察方法入参和返回值
watch com.example.UserService getUserInfo '{params, returnObj}' -x 2
# 参数说明:
# params: 方法入参数组
# returnObj: 返回值
# throwExp: 抛出的异常
# -x 2: 展开对象深度为 2 层
# 2. 添加条件:只观察特定参数的调用
watch com.example.UserService getUserInfo '{params, returnObj}' 'params[0]=="admin"' -x 2
# 3. 观察方法执行时间
watch com.example.UserService getUserInfo '{params, returnObj, #cost}' -x 2
# 4. 观察异常
watch com.example.UserService getUserInfo '{params, throwExp}' -e -x 2
# -e: 只在方法抛异常时观察
实战示例:
bash
# 场景:排查为什么某些用户查询很慢
watch com.example.UserService getUserInfo '{params[0], #cost}' '#cost > 1000' -x 2
# 只记录耗时超过 1000ms 的调用及其参数
4.2.2 trace - 追踪方法调用路径
bash
# 1. 追踪方法内部调用链路及耗时
trace com.example.UserService getUserInfo
# 输出示例:
# `---ts=2024-12-12 10:30:15;thread_name=http-nio-8080-exec-1;id=1f;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader
# `---[10.234567ms] com.example.UserService:getUserInfo()
# +---[0.123456ms] com.example.UserService:checkPermission() #33
# +---[8.345678ms] com.example.UserService:queryFromDB() #36
# | `---[8.123456ms] com.example.dao.UserDao:selectById() #42
# `---[1.234567ms] com.example.UserService:buildUserDTO() #39
# 2. 添加调用次数过滤(-n)
trace com.example.UserService getUserInfo -n 5
# 只追踪 5 次调用
# 3. 添加耗时过滤
trace com.example.UserService getUserInfo '#cost > 100'
# 只追踪耗时超过 100ms 的调用
核心价值:
- 快速定位慢方法
- 分析方法调用链路
- 识别性能瓶颈
4.2.3 stack - 查看方法调用堆栈
bash
# 查看某个方法被谁调用
stack com.example.UserService getUserInfo
# 输出示例:
# ts=2024-12-12 10:30:15;thread_name=http-nio-8080-exec-1;id=1f
# @com.example.controller.UserController.getUser()
# at com.example.UserService.getUserInfo(UserService.java:45)
# at com.example.controller.UserController.getUser(UserController.java:32)
# at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
# at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke()
# 应用场景:
# 1. 不知道某个方法在哪里被调用
# 2. 想确认调用链路是否符合预期
4.2.4 monitor - 方法执行统计
bash
# 统计方法执行情况
monitor -c 5 com.example.UserService getUserInfo
# 每 5 秒输出一次统计:
# timestamp class method total success fail avg-rt(ms) fail-rate
# 2024-12-12 10:30:15 UserService getUserInfo 120 118 2 45.6 1.67%
# 参数说明:
# -c 5: 每 5 秒统计一次
# total: 总调用次数
# success: 成功次数
# fail: 失败次数
# avg-rt: 平均响应时间
# fail-rate: 失败率
4.3 性能分析命令
4.3.1 dashboard - 实时监控面板
bash
dashboard
# 输出示例:
# ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTED DAEMON
# 17 http-nio-8080-exec-1 main 5 RUNNABLE 24.55% 0:12 false true
# -1 C2 CompilerThread0 - -1 - 3.14% 0:7 false true
# Memory used total max usage
# heap 512M 1024M 2048M 25.00%
# ps_eden_space 102M 256M 512M 19.92%
# ps_old_gen 410M 768M 1536M 26.69%
# Runtime
# os.name Linux
# os.version 5.4.0
# java.version 1.8.0_291
# java.home /usr/lib/jvm/java-1.8.0
关键指标:
%CPU:线程 CPU 占用率heap:堆内存使用情况ps_eden_space:新生代使用情况ps_old_gen:老年代使用情况
4.3.2 thread - 线程分析
bash
# 1. 查看所有线程
thread
# 2. 查看 CPU 占用最高的 3 个线程
thread -n 3
# 3. 查看特定线程的堆栈
thread 17
# 4. 查看死锁
thread -b
# 5. 查看处于 WAITING 状态的线程
thread -state WAITING
实战案例:CPU 飙高排查
bash
# 第一步:找到 CPU 占用最高的线程
thread -n 1
# 第二步:查看该线程的堆栈
thread 17
# 第三步:根据堆栈定位问题代码
# 常见原因:
# 1. 死循环
# 2. 大量对象创建
# 3. 频繁的 GC
4.3.3 profiler - 性能分析(火焰图)
bash
# 1. 启动性能采样(默认采样 CPU)
profiler start
# 2. 查看采样状态
profiler getSamples
# 3. 生成火焰图
profiler stop --format html --file /tmp/profile.html
# 4. 采样指定事件
profiler start --event alloc # 采样内存分配
profiler start --event lock # 采样锁竞争
火焰图解读:
- X 轴:方法占用 CPU 时间的比例
- Y 轴:调用栈深度
- 越宽的方法,CPU 占用越多
4.4 类增强与热更新
4.4.1 mc - 内存编译(Memory Compiler)
bash
# 编译 Java 文件
mc /tmp/UserService.java -d /tmp
# 输出:/tmp/com/example/UserService.class
4.4.2 redefine - 热更新类
bash
# 热更新类定义(JDK 的 Redefine 能力)
redefine /tmp/com/example/UserService.class
# 限制:
# 1. 不能新增或删除字段
# 2. 不能新增或删除方法
# 3. 只能修改方法体
4.4.3 retransform - 重新加载类
bash
# 使用 Arthas 增强后重新加载
retransform /tmp/com/example/UserService.class
# 与 redefine 的区别:
# redefine: JDK 原生能力,限制多
# retransform: Arthas 增强能力,更灵活
热修复完整流程:
bash
# 1. 反编译当前类
jad --source-only com.example.UserService > /tmp/UserService.java
# 2. 修改代码(使用 vim 或其他编辑器)
vim /tmp/UserService.java
# 3. 编译
mc /tmp/UserService.java -d /tmp
# 4. 热更新
redefine /tmp/com/example/UserService.class
# 5. 验证
watch com.example.UserService getUserInfo '{returnObj}' -x 2
4.5 其他实用命令
4.5.1 logger - 日志管理
bash
# 1. 查看日志配置
logger
# 2. 动态修改日志级别
logger --name ROOT --level debug
# 3. 查看特定 logger
logger --name com.example.UserService
4.5.2 ognl - 执行表达式
bash
# 1. 获取静态变量
ognl '@com.example.Config@MAX_SIZE'
# 2. 调用静态方法
ognl '@com.example.Utils@formatDate(new java.util.Date())'
# 3. 获取 Spring Bean
ognl '#springContext=@com.example.SpringContextHolder@getApplicationContext(), #springContext.getBean("userService")'
4.5.3 vmoption - 查看/修改 JVM 参数
bash
# 查看所有 JVM 参数
vmoption
# 查看特定参数
vmoption PrintGC
# 修改参数
vmoption PrintGCDetails true
五、底层原理深度剖析
5.1 核心技术栈
┌─────────────────────────────────────────────────────────┐
│ Arthas 技术架构 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────┐ │
│ │ 1. Java Agent(入口) │ │
│ │ - Instrumentation API │ │
│ │ - Attach API (JVM TI) │ │
│ └───────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────┐ │
│ │ 2. 字节码增强(核心) │ │
│ │ - ASM 字节码框架 │ │
│ │ - 方法拦截与增强 │ │
│ └───────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────┐ │
│ │ 3. 数据收集与分析 │ │
│ │ - 方法参数、返回值 │ │
│ │ - 执行时间、调用栈 │ │
│ └───────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────┐ │
│ │ 4. 命令执行与展示 │ │
│ │ - Netty Server │ │
│ │ - 命令行渲染 │ │
│ └───────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
5.2 启动流程详解
第一步:Attach 到目标 JVM
java
// Arthas 启动核心代码简化版
public class ArthasBootstrap {
public static void main(String[] args) {
// 1. 获取目标 JVM 进程 ID
String pid = selectJavaProcess();
// 2. 通过 Attach API 连接目标 JVM
VirtualMachine vm = VirtualMachine.attach(pid);
// 3. 加载 Arthas Agent
vm.loadAgent("/path/to/arthas-agent.jar", args);
// 4. Detach
vm.detach();
}
}
底层原理:
┌──────────────┐ Attach API ┌──────────────┐
│ Arthas CLI │ ────────────────────────→ │ Target JVM │
└──────────────┘ └──────────────┘
│ │
│ 1. attach(pid) │
├───────────────────────────────────────────→
│ │
│ 2. loadAgent(arthas-agent.jar) │
├───────────────────────────────────────────→
│ │
│ 3. 启动 Agent
│ 创建 Netty Server
│ │
│ 4. 通过 Socket 连接 │
←───────────────────────────────────────────┤
│ │
第二步:Agent 初始化
java
// arthas-agent.jar 的入口
public class AgentBootstrap {
public static void premain(String args, Instrumentation inst) {
// 1. 保存 Instrumentation 实例(核心能力)
ArthasBootstrap.instrumentation = inst;
// 2. 启动 Netty Server(默认 3658 端口)
startServer();
// 3. 注册 ClassFileTransformer(用于字节码增强)
inst.addTransformer(new ArthasTransformer(), true);
}
}
5.3 字节码增强原理
核心:Instrumentation + ASM
java
// 简化的字节码增强流程
public class ArthasTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
// 1. 使用 ASM 解析字节码
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
// 2. 使用 Visitor 模式遍历并修改字节码
ClassVisitor cv = new AdviceWeaver(cw, className);
cr.accept(cv, ClassReader.EXPAND_FRAMES);
// 3. 返回增强后的字节码
return cw.toByteArray();
}
}
watch 命令的实现原理
原始代码:
java
public class UserService {
public UserDTO getUserInfo(String userId) {
// 业务逻辑
return userDTO;
}
}
增强后的字节码(等价代码):
java
public class UserService {
public UserDTO getUserInfo(String userId) {
// ========== Arthas 插入的代码 START ==========
Object[] params = new Object[]{userId};
Object returnObj = null;
Throwable throwable = null;
long startTime = System.nanoTime();
try {
// ========== Arthas 插入的代码 END ==========
// 原始业务逻辑
returnObj = userDTO;
return userDTO;
// ========== Arthas 插入的代码 START ==========
} catch (Throwable t) {
throwable = t;
throw t;
} finally {
long cost = System.nanoTime() - startTime;
// 收集数据并发送到 Arthas Server
AdviceListener.onMethodEnd(
params, // 方法参数
returnObj, // 返回值
throwable, // 异常
cost // 耗时
);
}
// ========== Arthas 插入的代码 END ==========
}
}
数据流转:
原始方法
↓
字节码增强(ASM)
↓
插入监控代码
↓
方法执行时收集数据
↓
发送到 Arthas Server
↓
命令行展示
5.4 trace 命令实现原理
java
// trace 会在每个方法调用前后插入代码
public UserDTO getUserInfo(String userId) {
TraceEntity trace = new TraceEntity("getUserInfo");
try {
trace.begin("checkPermission");
checkPermission(userId);
trace.end("checkPermission");
trace.begin("queryFromDB");
User user = queryFromDB(userId);
trace.end("queryFromDB");
trace.begin("buildUserDTO");
UserDTO dto = buildUserDTO(user);
trace.end("buildUserDTO");
return dto;
} finally {
trace.print(); // 输出调用树
}
}
5.5 热更新原理
java
// redefine 底层实现
public void redefine(Class<?> clazz, byte[] newClassBytes) {
ClassDefinition definition = new ClassDefinition(clazz, newClassBytes);
// 使用 Instrumentation 的 redefineClasses
instrumentation.redefineClasses(definition);
}
JVM 层面的限制:
┌─────────────────────────────────────────┐
│ JVM Redefine 能力限制 │
├─────────────────────────────────────────┤
│ ✓ 可以修改: │
│ - 方法体 │
│ - 静态初始化器 │
│ │
│ ✗ 不可以: │
│ - 新增/删除字段 │
│ - 新增/删除方法 │
│ - 修改类层次结构 │
│ - 修改方法签名 │
└─────────────────────────────────────────┘
5.6 性能影响分析
Arthas 的性能开销:
- Attach 阶段: 几乎无影响(只在启动时)
- 字节码增强:
- 未开启监控:零影响
- 开启监控:<5% CPU 开销
- 数据收集:
- 每次方法调用增加 ~10-50 微秒
- 内存占用:<10MB
最佳实践:
bash
# 诊断完成后,及时停止监控
stop # 停止 Arthas,移除所有字节码增强
# 或者只针对特定方法监控
watch com.example.UserService specificMethod '{params}' -n 10
# -n 10: 只监控 10 次调用后自动停止
六、实战案例
案例 1:接口响应慢排查
问题描述:
- 用户反馈订单查询接口很慢
- 日志没有记录详细耗时
- 需要快速定位瓶颈
排查步骤:
bash
# 第一步:确认接口确实慢
monitor -c 5 com.example.OrderService getOrderDetail
# 输出:avg-rt(ms) = 3200ms,确认问题
# 第二步:追踪方法调用链路
trace com.example.OrderService getOrderDetail
# 输出:
# `---[3210.123456ms] com.example.OrderService:getOrderDetail()
# +---[10.123456ms] com.example.OrderService:checkPermission() #33
# +---[3180.345678ms] com.example.OrderService:queryOrderFromDB() #36
# | `---[3178.123456ms] com.example.dao.OrderDao:selectOrderDetail() #42
# `---[15.234567ms] com.example.OrderService:buildOrderVO() #39
# 第三步:发现问题在数据库查询,进一步追踪
trace com.example.dao.OrderDao selectOrderDetail
# 输出:
# `---[3178.123456ms] com.example.dao.OrderDao:selectOrderDetail()
# +---[5.123456ms] 准备 SQL
# +---[3170.345678ms] 执行 SQL ← 瓶颈在这里!
# `---[2.234567ms] 结果映射
# 第四步:查看具体 SQL 参数
watch com.example.dao.OrderDao selectOrderDetail '{params, returnObj}' -x 2
# 发现:查询条件没有使用索引,导致全表扫描
解决方案:
- 优化 SQL,添加索引
- 优化后验证:avg-rt 降至 50ms
案例 2:内存泄漏排查
问题描述:
- 应用运行一段时间后,内存持续增长
- 最终触发 OOM
排查步骤:
bash
# 第一步:实时监控内存情况
dashboard
# 观察到:
# heap: 1800M / 2048M (87.89%) ← 持续接近上限
# ps_old_gen: 1600M / 1536M ← 老年代几乎满了
# 第二步:查看堆内存占用
memory
# 第三步:触发 Full GC 后观察
vmtool --action forceGc
dashboard # 再次查看
# heap: 1750M / 2048M ← GC 后内存没有明显下降,确认泄漏
# 第四步:导出堆转储文件
heapdump /tmp/heap.hprof
# 第五步:使用 MAT 分析(离线)
# 发现:某个 Cache 对象占用 1.2GB
# 原因:缓存没有设置过期时间,无限增长
# 第六步:查看该缓存的使用情况
ognl '@com.example.CacheManager@getCache("userCache").size()'
# 输出:1234567 ← 缓存了 120 万条数据
# 第七步:临时清理(紧急修复)
ognl '@com.example.CacheManager@getCache("userCache").clear()'
# 第八步:查看源码,确认问题
jad com.example.CacheManager
# 发现:缓存初始化时没有设置 expireAfterWrite
解决方案:
java
// 修复代码
Cache<String, User> cache = CacheBuilder.newBuilder()
.maximumSize(10000) // 限制大小
.expireAfterWrite(1, TimeUnit.HOURS) // 1 小时过期
.build();
案例 3:CPU 100% 排查
问题描述:
- 应用 CPU 突然飙升至 100%
- 接口无响应
排查步骤:
bash
# 第一步:查看 CPU 占用最高的线程
thread -n 3
# 输出:
# "http-nio-8080-exec-25" Id=152 RUNNABLE (in native) CPU=89.50%
# at java.net.SocketInputStream.socketRead0(Native Method)
# at com.example.service.DataSyncService.syncData(DataSyncService.java:67)
# ...
# 第二步:查看该线程详细堆栈
thread 152
# 发现:DataSyncService.syncData 方法在执行
# 第三步:反编译查看代码
jad com.example.service.DataSyncService syncData
# 发现可疑代码:
# while (true) {
# if (queue.isEmpty()) {
# continue; ← 死循环,没有 sleep
# }
# processData(queue.poll());
# }
# 第四步:监控该方法的调用情况
monitor -c 5 com.example.service.DataSyncService syncData
# 输出:
# total=1000000 success=1000000 ← 5 秒内调用了 100 万次!
# 第五步:紧急热修复
jad --source-only com.example.service.DataSyncService > /tmp/DataSyncService.java
# 修改代码,添加 sleep
vim /tmp/DataSyncService.java
# while (true) {
# if (queue.isEmpty()) {
# Thread.sleep(100); ← 添加休眠
# continue;
# }
# processData(queue.poll());
# }
mc /tmp/DataSyncService.java -d /tmp
redefine /tmp/com/example/service/DataSyncService.class
# 第六步:验证
dashboard
# CPU: 5.2% ← 恢复正常
案例 4:类加载冲突
问题描述:
- 应用使用 Jackson 序列化时报错
- NoSuchMethodError: com.fasterxml.jackson.databind.ObjectMapper.xxx()
排查步骤:
bash
# 第一步:查找 ObjectMapper 类
sc -d com.fasterxml.jackson.databind.ObjectMapper
# 输出:
# class-info com.fasterxml.jackson.databind.ObjectMapper
# code-source file:/app/lib/jackson-databind-2.9.8.jar ← 版本 2.9.8
# classLoaderHash 7f31245a
# 第二步:查看方法是否存在
sm com.fasterxml.jackson.databind.ObjectMapper
# 发现:该方法在 2.9.8 版本中不存在(在 2.12+ 才有)
# 第三步:查找是否有多个 Jackson 版本
sc com.fasterxml.jackson.databind.*
# 发现:有多个 jar 包
# - jackson-databind-2.9.8.jar ← 被加载的版本
# - jackson-databind-2.13.0.jar ← 更新的版本
# 第四步:查看依赖关系(回到 Maven/Gradle)
# 发现:某个依赖强制使用了 2.9.8 版本
# 第五步:反编译查看调用代码
jad com.example.service.JsonService
# 确认:代码使用了 2.13.0 的新方法
解决方案:
xml
<!-- 排除旧版本依赖 -->
<dependency>
<groupId>com.old.library</groupId>
<artifactId>old-lib</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
案例 5:线程死锁
问题描述:
- 应用部分接口挂起,不响应
- 没有报错日志
排查步骤:
bash
# 第一步:检查是否有死锁
thread -b
# 输出:
# "Thread-A" Id=123 BLOCKED on java.lang.Object@12345678 owned by "Thread-B" Id=124
# at com.example.ServiceA.methodA(ServiceA.java:45)
# - waiting to lock <0x12345678> (a java.lang.Object)
#
# "Thread-B" Id=124 BLOCKED on java.lang.Object@87654321 owned by "Thread-A" Id=123
# at com.example.ServiceB.methodB(ServiceB.java:67)
# - waiting to lock <0x87654321> (a java.lang.Object)
# 第二步:查看死锁详情
thread 123
thread 124
# 第三步:反编译查看代码
jad com.example.ServiceA methodA
jad com.example.ServiceB methodB
# 发现:
# ServiceA.methodA:
# synchronized(lockA) {
# // ...
# synchronized(lockB) { ... }
# }
#
# ServiceB.methodB:
# synchronized(lockB) {
# // ...
# synchronized(lockA) { ... } ← 锁顺序相反!
# }
解决方案:
- 统一锁的获取顺序
- 或使用
Lock.tryLock()避免死锁
七、最佳实践
7.1 生产环境使用规范
1. 权限控制
bash
# 1. 限制可访问的用户
./as.sh --username admin --password xxx
# 2. 使用隧道模式(防止端口暴露)
./as.sh --tunnel-server 'ws://tunnel-server:7777/ws'
# 3. 配置 IP 白名单
./as.sh --target-ip 127.0.0.1
2. 性能保护
bash
# 1. 限制监控次数
watch com.example.Service method '{params}' -n 100
# 只监控 100 次后自动停止
# 2. 添加耗时过滤
trace com.example.Service method '#cost > 1000'
# 只追踪耗时超过 1 秒的调用
# 3. 限制输出深度
watch com.example.Service method '{params}' -x 2
# 只展开对象 2 层,避免输出过大
3. 使用完毕立即停止
bash
# 停止所有增强
reset # 重置所有被增强的类
# 完全退出
stop # 停止 Arthas Server
7.2 常用场景速查表
| 场景 | 命令 | 说明 |
|---|---|---|
| 接口慢 | trace |
追踪方法调用链路及耗时 |
| CPU 高 | thread -n 3 |
查看 CPU 占用最高的线程 |
| 内存高 | heapdump + memory |
导出堆转储分析 |
| 线程卡死 | thread -b |
检查死锁 |
| 类冲突 | sc -d + jad |
查看类来源及实际代码 |
| 异常监控 | watch -e |
只在抛异常时观察 |
| 日志级别 | logger --level |
动态修改日志级别 |
| 方法统计 | monitor -c 5 |
统计方法执行情况 |
7.3 组合技巧
技巧 1:定位慢 SQL
bash
# 1. 找到慢的 Service 方法
trace com.example.OrderService getOrder '#cost > 1000'
# 2. 继续追踪到 DAO 层
trace com.example.dao.OrderDao selectOrder
# 3. 观察 SQL 参数
watch com.example.dao.OrderDao selectOrder '{params, #cost}'
技巧 2:排查间歇性问题
bash
# 持续监控,记录所有异常情况
watch com.example.Service method '{params, throwExp, #cost}' -e >> /tmp/error.log &
# 后台运行,持续记录
# 等待问题复现后分析日志
技巧 3:验证修复效果
bash
# 修复前
monitor -c 10 com.example.Service method
# 记录 avg-rt 和 fail-rate
# 热更新代码
redefine /tmp/Service.class
# 修复后
monitor -c 10 com.example.Service method
# 对比数据,验证效果
八、常见问题与解决方案
Q1:Arthas 无法 attach 到 JVM
现象:
[ERROR] Can not attach to target java process, pid: 12345
原因与解决:
bash
# 1. 权限不足
# 解决:使用与目标进程相同的用户启动 Arthas
sudo -u <app-user> java -jar arthas-boot.jar
# 2. JVM 参数限制
# 解决:添加 JVM 参数
-XX:+StartAttachListener
# 3. 容器环境问题
# 解决:共享 PID namespace
docker run --pid=host ...
Q2:redefine 失败
现象:
[ERROR] redefine error! class: com.example.UserService
原因与解决:
bash
# 1. 检查是否修改了方法签名
# Redefine 不支持新增/删除方法或字段
# 解决:只修改方法体
# 2. 编译版本不匹配
# 解决:使用相同 JDK 版本编译
mc -d /tmp UserService.java
# 3. 类已被增强
# 解决:先 reset,再 redefine
reset com.example.UserService
redefine /tmp/UserService.class
Q3:watch 命令看不到返回值
现象:
watch com.example.Service method '{returnObj}'
# returnObj 一直是 null
原因与解决:
bash
# 1. 方法还没执行完(默认在方法入口观察)
# 解决:添加 -s 参数(在方法返回时观察)
watch com.example.Service method '{returnObj}' -s -x 2
# 2. 展开深度不够
# 解决:增加 -x 参数
watch com.example.Service method '{returnObj}' -x 3
Q4:影响生产性能
现象:
- 开启 trace 后,应用响应变慢
- CPU 使用率上升
解决:
bash
# 1. 添加过滤条件,减少监控频率
trace com.example.Service method '#cost > 500'
# 2. 限制监控次数
trace com.example.Service method -n 50
# 3. 使用完立即停止
stop
Q5:命令输出被截断
现象:
watch com.example.Service method '{params}'
# 输出显示 ... (省略)
解决:
bash
# 1. 增加展开深度
watch com.example.Service method '{params}' -x 5
# 2. 增加输出长度限制
options json-max-depth 5
九、总结
9.1 核心价值
┌─────────────────────────────────────────────────────────┐
│ Arthas 解决的核心问题 │
├─────────────────────────────────────────────────────────┤
│ │
│ 传统方式 Arthas 方式 │
│ ───────────────────────────────────────────── │
│ 加日志 → 重启 → 等待 实时监控,立即定位 │
│ 影响业务 → 零侵入,不重启 │
│ 周期长(小时) → 快速(分钟) │
│ 需要发版 → 热修复 │
│ 定位困难 → 精准追踪 │
│ │
└─────────────────────────────────────────────────────────┘
9.2 适用场景
强烈推荐:
- ✅ 生产环境紧急问题排查
- ✅ 性能瓶颈分析与优化
- ✅ 线上 Bug 临时修复
- ✅ 类加载冲突诊断
不适用:
- ❌ 开发环境日常调试(用 IDE 更方便)
- ❌ 大规模数据分析(用专业 APM)
- ❌ 长期监控(用 Prometheus + Grafana)
9.3 学习路径
Level 1: 入门(1 天)
├─ dashboard, thread
├─ sc, sm, jad
└─ watch(核心)
Level 2: 进阶(3 天)
├─ trace(性能分析)
├─ monitor(统计)
├─ stack(调用链)
└─ profiler(火焰图)
Level 3: 高级(1 周)
├─ redefine(热更新)
├─ ognl(表达式)
├─ 字节码增强原理
└─ 实战案例演练
9.4 关键命令速记
bash
# 五大核心命令(掌握这些就够用 80% 场景)
1. dashboard # 实时监控面板
2. thread # 线程分析
3. watch # 观察方法调用(最常用)
4. trace # 追踪方法调用链路
5. jad # 反编译查看代码
# 进阶命令
6. monitor # 方法执行统计
7. redefine # 热更新类
8. profiler # 性能分析(火焰图)