Arthas 完全指南:原理与实战

一、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 的性能开销:

  1. Attach 阶段: 几乎无影响(只在启动时)
  2. 字节码增强:
    • 未开启监控:零影响
    • 开启监控:<5% CPU 开销
  3. 数据收集:
    • 每次方法调用增加 ~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:类加载冲突

问题描述:

排查步骤:

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   # 性能分析(火焰图)
相关推荐
降临-max4 小时前
JavaWeb企业级开发---快速入门、请求响应、分层解耦
java·开发语言·笔记·学习
熊猫比分管理员4 小时前
免费开源代码/免费搭建体育直播系统;赛程、直播、专家卖料三大核心全解析
java
摇滚侠5 小时前
面试实战 问题三十五 Spring bean 的自动装配 介绍一下熟悉的几种设计模式 Java 四种线程池是哪些
java·spring·面试
2301_805962935 小时前
嘉立创EDA添加自己的元件和封装
java·开发语言
TimberWill5 小时前
MinIO整合SpringBoot实现获取文件夹目录结构及文件内容
java·linux·springboot
崎岖Qiu5 小时前
【设计模式笔记18】:并发安全与双重检查锁定的单例模式
java·笔记·单例模式·设计模式
曲莫终5 小时前
spring.main.lazy-initialization配置的实现机制
java·后端·spring
CodersCoder5 小时前
SpringAI-Tool简单实践
spring·spring-ai
❀͜͡傀儡师5 小时前
docker部署Docker Compose文件Web管理工具Dockman
java·前端·docker·dockman