Skip to content

Arthas 在线诊断 ​

阿里巴巴 Java 诊断工具,生产环境问题排查利器。 Arthas(阿尔萨斯)是 Alibaba 开源的 Java 诊断工具,支持在线上环境实时诊断,无需修改代码或重启应用,让开发者能够以极低的代价定位生产环境问题。


目录 ​

  1. Arthas 概述
  2. 安装与启动
  3. Dashboard
  4. 核心命令——类与方法相关
  5. 核心命令——方法观测
  6. 核心命令——其他
  7. 实战场景
  8. 最佳实践

Arthas 概述 ​

什么是 Arthas ​

Arthas 是 Alibaba 开源的 Java 诊断工具,主要解决以下痛点:

  • 线上代码是否真的发布了预期版本?
  • 某个接口为什么响应慢?慢在哪里?
  • 如何在不重启的情况下修改线上代码?
  • 线上 JVM 内存暴涨,如何快速定位?
  • 为什么 CPU 突然飙升到 100%?

Arthas 基于 Java Agent 机制,通过字节码增强技术动态附加到目标 JVM 进程上,支持 JDK 6+,对业务代码零侵入。

与传统排查方式对比 ​

场景传统方式Arthas 方式
看代码是否生效登录服务器找到 class 文件,javap 反编译jad 命令一行搞定
修改线上代码修改源码、编译、打包、发布、重启jad + mc + redefine 热替换
看方法入参、返回值加日志、重启、复现watch 命令动态观测
接口慢逐段打日志、猜测瓶颈trace 命令自动输出链路耗时
CPU 高jstack 抓线程栈,手工分析thread -n 直接找到最忙线程
OOM启动时加 OOM 参数,重启后等复现heapdump 动态 dump,vmtool 查对象
改日志级别修改 logback.xml、重启ognl 一行命令实时修改
获取 Spring Bean写临时 Controller 暴露接口ognl 直接获取任意 Bean

适用场景 ​

  • 线上问题定位:CPU 飙升、内存泄漏、线程死锁、接口响应慢
  • 无代码侵入排查:不改一行代码,不重启应用
  • 代码与配置确认:验证发布版本是否正确、配置是否生效
  • 热修复:紧急 bug 无需发版,直接热替换 class
  • 性能诊断:方法级耗时统计、调用链路追踪

安装与启动 ​

前提条件 ​

  • JDK 6 及以上
  • 有目标 Java 进程的 PID(可通过 jps -l 或 ps -ef | grep java 获取)
  • 有操作系统的用户权限(如无特殊权限要求,普通用户即可)

arthas-boot.jar 启动(推荐) ​

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

# 启动,交互式选择目标进程
java -jar arthas-boot.jar

# 或直接指定 PID
java -jar arthas-boot.jar <PID>
# 下载 arthas-boot.jar
curl -O https://arthas.aliyun.com/arthas-boot.jar

# 启动,交互式选择目标进程
java -jar arthas-boot.jar

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

启动后进入交互式命令行界面,显示如下信息:

[INFO] arthas-boot version: 3.7.1
[INFO] Found existing java process, please choose one and hit RETURN.
* [1]: 12345 com.example.MyApplication
  [2]: 67890 org.apache.catalina.startup.Bootstrap
[INFO] arthas-boot version: 3.7.1
[INFO] Found existing java process, please choose one and hit RETURN.
* [1]: 12345 com.example.MyApplication
  [2]: 67890 org.apache.catalina.startup.Bootstrap

输入序号即可 attach 到对应进程。

as.sh 一键启动(Linux / Mac) ​

bash
# 一键安装并启动
curl -L https://arthas.aliyun.com/install.sh | sh

# 启动(交互式选择进程)
./as.sh

# 指定 PID
./as.sh <PID>

# 指定 IP 和端口(tunnel server 模式)
./as.sh <PID> --tunnel-server http://127.0.0.1:7777
# 一键安装并启动
curl -L https://arthas.aliyun.com/install.sh | sh

# 启动(交互式选择进程)
./as.sh

# 指定 PID
./as.sh <PID>

# 指定 IP 和端口(tunnel server 模式)
./as.sh <PID> --tunnel-server http://127.0.0.1:7777

Windows 启动 ​

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

# 启动
java -jar arthas-boot.jar

# 或者使用 as.bat(在 bin 目录下)
as.bat <PID>
# 下载 as.bat
curl -O https://arthas.aliyun.com/arthas-boot.jar

# 启动
java -jar arthas-boot.jar

# 或者使用 as.bat(在 bin 目录下)
as.bat <PID>

Windows 下建议使用 java -jar arthas-boot.jar 方式,as.bat 在某些 Windows 版本上可能遇到问题。

通过 Spring Boot Starter 集成 ​

在 pom.xml 中添加依赖:

xml
<dependency>
    <groupId>com.taobao.arthas</groupId>
    <artifactId>arthas-spring-boot-starter</artifactId>
    <version>${arthas.version}</version>
</dependency>
<dependency>
    <groupId>com.taobao.arthas</groupId>
    <artifactId>arthas-spring-boot-starter</artifactId>
    <version>${arthas.version}</version>
</dependency>

application.yml 配置:

yaml
arthas:
  agent-id: my-app-001
  tunnel-server: ws://127.0.0.1:7777/ws
  app-name: my-app
arthas:
  agent-id: my-app-001
  tunnel-server: ws://127.0.0.1:7777/ws
  app-name: my-app

通过 Spring Boot Starter 集成后,可以通过 Tunnel Server 远程连接到目标应用,适合容器化(Docker/K8s)环境。

常用启动参数 ​

参数说明
--target-ip <IP>监听 IP,默认 127.0.0.1
--telnet-port <PORT>Telnet 端口,默认 3658
--http-port <PORT>HTTP 端口,默认 8563
--tunnel-server <URL>Tunnel Server 地址
--agent-id <ID>Agent ID,用于 Tunnel Server 识别
--app-name <NAME>应用名称
--tunnel-agent使用长连接方式连接 Tunnel Server
--session-timeout <SEC>Session 超时时间
--height <ROWS>输出行数限制

示例:

bash
java -jar arthas-boot.jar <PID> --http-port 8563 --session-timeout 3600
java -jar arthas-boot.jar <PID> --http-port 8563 --session-timeout 3600

非交互式执行命令 ​

Arthas 支持批量模式(Batch Mode),一次执行多条命令后退出:

bash
# 执行单条命令
java -jar arthas-boot.jar <PID> -c "dashboard -n 1"

# 执行命令文件
java -jar arthas-boot.jar <PID> -f /tmp/commands.txt
# 执行单条命令
java -jar arthas-boot.jar <PID> -c "dashboard -n 1"

# 执行命令文件
java -jar arthas-boot.jar <PID> -f /tmp/commands.txt

commands.txt 示例:

sc -d com.example.service.UserService
watch com.example.service.UserService getUser "{params,returnObj}" -x 2
sc -d com.example.service.UserService
watch com.example.service.UserService getUser "{params,returnObj}" -x 2

Dashboard ​

Dashboard 是 Arthas 的实时仪表盘,输入 dashboard 即可查看当前 JVM 的概要信息。

bash
# 查看 dashboard,默认每 5 秒刷新
dashboard

# 只输出一次,不持续刷新
dashboard -n 1

# 指定刷新间隔(秒)
dashboard -i 2000
# 查看 dashboard,默认每 5 秒刷新
dashboard

# 只输出一次,不持续刷新
dashboard -n 1

# 指定刷新间隔(秒)
dashboard -i 2000

Dashboard 输出包含以下几个关键区域:

1. 线程信息 ​

ID     NAME                         GROUP          PRIORITY   STATE      %CPU      TIME     INTERRUPTED
22     Catalina-utility-1           main            5          TIMED_WAITING 0.1   0:0:0    false
21     http-nio-8080-exec-10        main            5          WAITING   0.0       0:0:0    false
...
ID     NAME                         GROUP          PRIORITY   STATE      %CPU      TIME     INTERRUPTED
22     Catalina-utility-1           main            5          TIMED_WAITING 0.1   0:0:0    false
21     http-nio-8080-exec-10        main            5          WAITING   0.0       0:0:0    false
...
  • ID:线程 ID(native 级别)
  • NAME:线程名
  • %CPU:CPU 使用率
  • TIME:累计 CPU 时间

2. 内存信息 ​

Memory                    used      total     max        usage     GC
heap                      2.1G      4.0G      4.0G      52.50%
  eden_space              1.2G      2.0G      2.0G      60.00%
  survivor_space          128M      256M      256M      50.00%
  old_gen                 768M      1.7G      1.7G      45.18%
nonheap                   256M      512M      -1         50.00%
  metaspace               200M      400M      -1         50.00%
Memory                    used      total     max        usage     GC
heap                      2.1G      4.0G      4.0G      52.50%
  eden_space              1.2G      2.0G      2.0G      60.00%
  survivor_space          128M      256M      256M      50.00%
  old_gen                 768M      1.7G      1.7G      45.18%
nonheap                   256M      512M      -1         50.00%
  metaspace               200M      400M      -1         50.00%

3. GC 信息 ​

gc.ps_scavenge.count              = 128
gc.ps_scavenge.time(ms)           = 8960
gc.ps_marksweep.count             = 4
gc.ps_marksweep.time(ms)          = 2480
gc.ps_scavenge.count              = 128
gc.ps_scavenge.time(ms)           = 8960
gc.ps_marksweep.count             = 4
gc.ps_marksweep.time(ms)          = 2480

4. 运行时信息 ​

Runtime
  OS                               Windows 10
  JVM                              Java HotSpot(TM) 64-Bit Server VM
  Java version                     11.0.12
  Loaded class count               12589
  Total loaded class count         12592
  Unloaded class count             3
  Thread count                     45
  Daemon thread count              38
  Peak thread count                68
  Started thread count             148
Runtime
  OS                               Windows 10
  JVM                              Java HotSpot(TM) 64-Bit Server VM
  Java version                     11.0.12
  Loaded class count               12589
  Total loaded class count         12592
  Unloaded class count             3
  Thread count                     45
  Daemon thread count              38
  Peak thread count                68
  Started thread count             148

核心命令——类与方法相关 ​

sc(Search Class) ​

搜索已加载的类信息,类似于 JVM 层面的 find 命令。

bash
# 查看类的详细信息
sc -d com.example.service.UserService

# 模糊搜索
sc *.UserService

# 查看类的方法签名
sc -d com.example.service.UserService

# 搜索指定接口的实现类
sc -i com.example.service.UserService

# 输出类的 classLoader 信息
sc -c <classLoaderHash> com.example.service.UserService
# 查看类的详细信息
sc -d com.example.service.UserService

# 模糊搜索
sc *.UserService

# 查看类的方法签名
sc -d com.example.service.UserService

# 搜索指定接口的实现类
sc -i com.example.service.UserService

# 输出类的 classLoader 信息
sc -c <classLoaderHash> com.example.service.UserService

参数说明:

参数说明
-d输出类的详细信息(包括 classLoader、注解、接口等)
-f输出类的字段信息
-x展开层次
-i搜索匹配接口的所有实现类
-c指定 classLoader 的 hash

输出示例:

bash
$ sc -d com.example.service.UserService
 class-info        com.example.service.UserService
 code-source       /app/service-1.0.0.jar
 name              com.example.service.UserService
 isInterface       false
 isAnnotation      false
 isEnum            false
 isAnonymousClass  false
 isArray           false
 isMemberClass     false
 isSynthetic       false
 simple-name       UserService
 modifier          public
 annotation        org.springframework.stereotype.Service
 interfaces        com.example.api.UserApi
 super-class       +-java.lang.Object
 class-loader      +-org.springframework.boot.loader.LaunchedURLClassLoader@5674cd4d
 classLoaderHash   5674cd4d
$ sc -d com.example.service.UserService
 class-info        com.example.service.UserService
 code-source       /app/service-1.0.0.jar
 name              com.example.service.UserService
 isInterface       false
 isAnnotation      false
 isEnum            false
 isAnonymousClass  false
 isArray           false
 isMemberClass     false
 isSynthetic       false
 simple-name       UserService
 modifier          public
 annotation        org.springframework.stereotype.Service
 interfaces        com.example.api.UserApi
 super-class       +-java.lang.Object
 class-loader      +-org.springframework.boot.loader.LaunchedURLClassLoader@5674cd4d
 classLoaderHash   5674cd4d

sm(Search Method) ​

搜索已加载类的方法。

bash
# 查看类的所有方法
sm com.example.service.UserService

# 查看方法的详细信息
sm -d com.example.service.UserService getUser

# 按方法名模糊搜索
sm com.example.service.UserService get*
# 查看类的所有方法
sm com.example.service.UserService

# 查看方法的详细信息
sm -d com.example.service.UserService getUser

# 按方法名模糊搜索
sm com.example.service.UserService get*

jad(反编译) ​

反编译已加载的类,用于验证线上代码是否与源码一致。

bash
# 反编译整个类
jad com.example.service.UserService

# 只反编译指定方法
jad com.example.service.UserService getUser

# 不显示行号
jad --source-only com.example.service.UserService

# 指定 classLoader
jad -c <classLoaderHash> com.example.service.UserService
# 反编译整个类
jad com.example.service.UserService

# 只反编译指定方法
jad com.example.service.UserService getUser

# 不显示行号
jad --source-only com.example.service.UserService

# 指定 classLoader
jad -c <classLoaderHash> com.example.service.UserService

典型场景:

bash
# 排查:线上代码是否已包含某次修改?
jad com.example.service.UserService

# 输出源码中是否有预期的逻辑
# 如果看到的是旧代码,说明发布未生效
# 排查:线上代码是否已包含某次修改?
jad com.example.service.UserService

# 输出源码中是否有预期的逻辑
# 如果看到的是旧代码,说明发布未生效

输出示例:

java
$ jad com.example.service.UserService

ClassLoader:
+-org.springframework.boot.loader.LaunchedURLClassLoader@5674cd4d

Location:
/app/service-1.0.0.jar

/*
 * Decompiled with CFR.
 */
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public User getUser(Long id) {
        // 此处可以看到线上代码是否包含预期逻辑
        return userMapper.selectById(id);
    }
}
$ jad com.example.service.UserService

ClassLoader:
+-org.springframework.boot.loader.LaunchedURLClassLoader@5674cd4d

Location:
/app/service-1.0.0.jar

/*
 * Decompiled with CFR.
 */
package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public User getUser(Long id) {
        // 此处可以看到线上代码是否包含预期逻辑
        return userMapper.selectById(id);
    }
}

mc + redefine(热替换 Class) ​

在不重启进程的情况下,用编译后的新 class 替换已加载的 class。

bash
# 第一步:修改源码后,编译
mc /tmp/UserService.java -d /tmp/output

# 指定 classLoader 编译
mc -c <classLoaderHash> /tmp/UserService.java -d /tmp/output

# 第二步:热替换
redefine /tmp/output/com/example/service/UserService.class

# 一次替换多个 class
redefine -c <classLoaderHash> /tmp/output/com/example/service/UserService.class /tmp/output/com/example/service/OrderService.class
# 第一步:修改源码后,编译
mc /tmp/UserService.java -d /tmp/output

# 指定 classLoader 编译
mc -c <classLoaderHash> /tmp/UserService.java -d /tmp/output

# 第二步:热替换
redefine /tmp/output/com/example/service/UserService.class

# 一次替换多个 class
redefine -c <classLoaderHash> /tmp/output/com/example/service/UserService.class /tmp/output/com/example/service/OrderService.class

注意事项:

  • redefine 不会触发类的初始化,静态代码块不会再次执行。
  • 不能增加、删除字段和方法(签名不可变)。
  • 不能修改父类或实现的接口。
  • 生产环境热替换需谨慎,建议配合应急流程。
  • 替换后的变更在 JVM 重启后丢失。

典型流程:

bash
# 1. 先反编译线上代码到本地文件
jad --source-only com.example.service.UserService > /tmp/UserService.java

# 2. 修改逻辑(例如加一个空判断)
# 3. 编译
mc /tmp/UserService.java -d /tmp/output

# 4. 热替换
redefine /tmp/output/com/example/service/UserService.class

# 5. 验证
jad com.example.service.UserService
# 1. 先反编译线上代码到本地文件
jad --source-only com.example.service.UserService > /tmp/UserService.java

# 2. 修改逻辑(例如加一个空判断)
# 3. 编译
mc /tmp/UserService.java -d /tmp/output

# 4. 热替换
redefine /tmp/output/com/example/service/UserService.class

# 5. 验证
jad com.example.service.UserService

dump(dump class 到本地) ​

将 JVM 中已加载的 class 文件 dump 到本地磁盘。

bash
# dump 指定类
dump com.example.service.UserService

# dump 匹配的所有类
dump *.UserService

# 指定输出目录
dump -d /tmp/dump com.example.service.UserService
# dump 指定类
dump com.example.service.UserService

# dump 匹配的所有类
dump *.UserService

# 指定输出目录
dump -d /tmp/dump com.example.service.UserService

典型用途:将线上 class dump 到本地后,用专业反编译工具(如 JD-GUI)进一步分析。


核心命令——方法观测 ​

watch(方法执行数据观测) ​

观测方法调用的入参、返回值、异常、耗时等信息,是 Arthas 最常用的命令之一。

bash
# 观察方法返回值
watch com.example.service.UserService getUser "{params,returnObj}"

# 观察异常
watch com.example.service.UserService getUser "{params,throwExp}" -e

# 条件过滤:只观测耗时超过 100ms 的调用
watch com.example.service.UserService getUser "{params,returnObj}" '#cost>100'

# 条件过滤:只观测 id=100 的调用
watch com.example.service.UserService getUser "{params,returnObj}" "params[0]==100"

# 展开深度
watch com.example.service.UserService getUser "{params,returnObj}" -x 3

# 观测多次后退出
watch com.example.service.UserService getUser "{params,returnObj}" -n 5
# 观察方法返回值
watch com.example.service.UserService getUser "{params,returnObj}"

# 观察异常
watch com.example.service.UserService getUser "{params,throwExp}" -e

# 条件过滤:只观测耗时超过 100ms 的调用
watch com.example.service.UserService getUser "{params,returnObj}" '#cost>100'

# 条件过滤:只观测 id=100 的调用
watch com.example.service.UserService getUser "{params,returnObj}" "params[0]==100"

# 展开深度
watch com.example.service.UserService getUser "{params,returnObj}" -x 3

# 观测多次后退出
watch com.example.service.UserService getUser "{params,returnObj}" -n 5

参数说明:

参数说明
{params,returnObj,throwExp}观察表达式,支持多个变量
-e只观测异常抛出时的调用
-f只观测方法正常返回(默认同时观测正常返回和异常)
-s只观测方法调用开始
-b在方法调用之前观测(此时 returnObj/throwExp 为空)
-x <N>展开结果的深度,默认 1
-n <N>观测 N 次后退出
#cost>100过滤耗时大于 100ms 的调用
"condition"条件表达式,如 params[0]==100

观察表达式变量:

变量说明
params方法参数数组
returnObj返回值
throwExp异常对象
target当前对象(this)
method当前方法对象
args同 params
#cost方法执行耗时(ms)

实用示例:

bash
# 线上排查:看某个方法返回了什么奇怪的值
watch com.example.service.OrderService getOrder "{returnObj}" -x 2

# 排查异常:谁在抛异常
watch com.example.service.PaymentService pay "{params,throwExp}" -e -x 2

# 慢调用排查:看哪些请求耗时超过 500ms
watch com.example.service.ReportService generateReport "{params,returnObj}" '#cost>500' -x 2
# 线上排查:看某个方法返回了什么奇怪的值
watch com.example.service.OrderService getOrder "{returnObj}" -x 2

# 排查异常:谁在抛异常
watch com.example.service.PaymentService pay "{params,throwExp}" -e -x 2

# 慢调用排查:看哪些请求耗时超过 500ms
watch com.example.service.ReportService generateReport "{params,returnObj}" '#cost>500' -x 2

trace(方法内部调用链路 + 耗时) ​

trace 是排查接口慢的利器——它会输出方法内部所有子调用的耗时分布。

bash
# 追踪方法的调用链路
trace com.example.service.OrderService createOrder

# 过滤耗时 > 50ms 的节点
trace com.example.service.OrderService createOrder '#cost > 50'

# 跳过 JDK 类
trace --skip-jdk com.example.service.OrderService createOrder

# 限制追踪深度
trace com.example.service.OrderService createOrder -m 3

# 输出 N 次后停止
trace com.example.service.OrderService createOrder -n 3

# 按耗时阈值过滤,只显示开销大的调用路径
trace com.example.service.OrderService createOrder '#cost > 100'
# 追踪方法的调用链路
trace com.example.service.OrderService createOrder

# 过滤耗时 > 50ms 的节点
trace com.example.service.OrderService createOrder '#cost > 50'

# 跳过 JDK 类
trace --skip-jdk com.example.service.OrderService createOrder

# 限制追踪深度
trace com.example.service.OrderService createOrder -m 3

# 输出 N 次后停止
trace com.example.service.OrderService createOrder -n 3

# 按耗时阈值过滤,只显示开销大的调用路径
trace com.example.service.OrderService createOrder '#cost > 100'

输出示例:

bash
$ trace com.example.service.OrderService createOrder
`---[0.123456ms] com.example.service.OrderService:createOrder()
    +---[0.002ms] com.example.service.OrderService:validateParams() #0
    +---[0.001ms] com.example.service.OrderService:checkStock() #1
    +---[0.100ms] com.example.service.OrderService:calculatePrice() #2
    |   `---[0.095ms] com.example.service.PriceEngine:calculate()
    |       `---[0.090ms] com.example.service.DiscountCalculator:applyDiscount()
    +---[0.015ms] com.example.service.OrderService:saveOrder() #3
    `---[0.002ms] com.example.service.OrderService:sendNotification() #4
$ trace com.example.service.OrderService createOrder
`---[0.123456ms] com.example.service.OrderService:createOrder()
    +---[0.002ms] com.example.service.OrderService:validateParams() #0
    +---[0.001ms] com.example.service.OrderService:checkStock() #1
    +---[0.100ms] com.example.service.OrderService:calculatePrice() #2
    |   `---[0.095ms] com.example.service.PriceEngine:calculate()
    |       `---[0.090ms] com.example.service.DiscountCalculator:applyDiscount()
    +---[0.015ms] com.example.service.OrderService:saveOrder() #3
    `---[0.002ms] com.example.service.OrderService:sendNotification() #4

从输出可以一眼看出时间花在了哪个方法上(DiscountCalculator.applyDiscount 耗时 0.090ms,占了主要部分)。

注意:trace 的性能开销较大,生产环境建议配合条件表达式使用,避免大流量下开启。

常见用法场景:

bash
# 只追踪特定入参的慢调用
trace com.example.service.OrderService createOrder "params[0].userId==12345" '#cost>200'
# 只追踪特定入参的慢调用
trace com.example.service.OrderService createOrder "params[0].userId==12345" '#cost>200'

stack(方法调用栈) ​

查看当前方法的调用栈,用于回答"这个方法被谁调用了"。

bash
# 查看方法的调用栈
stack com.example.service.UserService getUser

# 条件过滤
stack com.example.service.UserService getUser "params[0]==100"

# 限制输出次数
stack com.example.service.UserService getUser -n 3
# 查看方法的调用栈
stack com.example.service.UserService getUser

# 条件过滤
stack com.example.service.UserService getUser "params[0]==100"

# 限制输出次数
stack com.example.service.UserService getUser -n 3

输出示例:

bash
$ stack com.example.service.UserService getUser
method com.example.service.UserService.getUser location:
    at com.example.service.UserService.getUser(UserService.java:25)
    at com.example.controller.UserController.detail(UserController.java:45)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
    ...
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:256)
$ stack com.example.service.UserService getUser
method com.example.service.UserService.getUser location:
    at com.example.service.UserService.getUser(UserService.java:25)
    at com.example.controller.UserController.detail(UserController.java:45)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
    ...
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:256)

tt(Time Tunnel,时间隧道) ​

tt 命令记录方法的每次调用,并支持事后回放。

bash
# 记录方法的每次调用
tt -t com.example.service.UserService getUser

# 限制记录次数
tt -t com.example.service.UserService getUser -n 5

# 限制记录深度
tt -t com.example.service.UserService getUser -x 2

# 查看记录列表
tt -l

# 查看某次记录详情
tt -i 1000

# 查看方法的调用参数
tt -i 1000 -p

# 重放某次调用
tt -i 1000 --replay

# 重放时跳过观察表达式
tt -i 1000 --replay -x 2

# 清空记录
tt -c
# 记录方法的每次调用
tt -t com.example.service.UserService getUser

# 限制记录次数
tt -t com.example.service.UserService getUser -n 5

# 限制记录深度
tt -t com.example.service.UserService getUser -x 2

# 查看记录列表
tt -l

# 查看某次记录详情
tt -i 1000

# 查看方法的调用参数
tt -i 1000 -p

# 重放某次调用
tt -i 1000 --replay

# 重放时跳过观察表达式
tt -i 1000 --replay -x 2

# 清空记录
tt -c

参数说明:

参数说明
-t记录方法调用
-l列出所有记录
-i <N>查看或重放指定索引的记录
-p打印方法的调用参数
--replay重放某次调用
-x <N>展开结果深度
-n <N>记录次数上限
-c清空所有记录

典型场景:

bash
# 第一次记录
tt -t com.example.service.OrderService createOrder -n 10

# 调用几次接口触发后,查看记录列表
tt -l

# 选择索引为 1000 的记录,查看详情
tt -i 1000

# 重放该次调用(参数完全相同,不依赖外部 mock)
tt -i 1000 --replay
# 第一次记录
tt -t com.example.service.OrderService createOrder -n 10

# 调用几次接口触发后,查看记录列表
tt -l

# 选择索引为 1000 的记录,查看详情
tt -i 1000

# 重放该次调用(参数完全相同,不依赖外部 mock)
tt -i 1000 --replay

使用注意:

  • tt 会持有对象的引用,禁止长时间开启,用完及时 -c 清空。
  • 重放时调用的时间戳是当前时间,不是原时间。
  • 重放依赖的方法内部逻辑与记录时一致,否则可能得到不同结果。

monitor(方法调用统计) ​

对指定方法进行周期性统计(默认 60 秒一个周期)。

bash
# 统计方法调用
monitor com.example.service.UserService getUser

# 指定统计周期(秒)
monitor -c 30 com.example.service.UserService getUser

# 条件过滤
monitor com.example.service.UserService getUser "params[0]==100"
# 统计方法调用
monitor com.example.service.UserService getUser

# 指定统计周期(秒)
monitor -c 30 com.example.service.UserService getUser

# 条件过滤
monitor com.example.service.UserService getUser "params[0]==100"

输出示例:

bash
$ monitor -c 30 com.example.service.UserService getUser
Monitoring com.example.service.UserService.getUser
30s timestamp: 2024-01-15 10:30:00
method                  calls  avg(ms)  max(ms)  min(ms)  error%  success%
com.example.service.UserService.getUser
                        125    12.3     156      0.1      0.00    100.00%
$ monitor -c 30 com.example.service.UserService getUser
Monitoring com.example.service.UserService.getUser
30s timestamp: 2024-01-15 10:30:00
method                  calls  avg(ms)  max(ms)  min(ms)  error%  success%
com.example.service.UserService.getUser
                        125    12.3     156      0.1      0.00    100.00%

监控指标说明:

指标说明
calls调用次数
avg(ms)平均耗时
max(ms)最大耗时
min(ms)最小耗时
error%错误率
success%成功率

核心命令——其他 ​

ognl(执行任意表达式) ​

OGNL 是 Arthas 中最强大的命令,可以在线执行任意 Java 表达式。

bash
# 获取静态字段值
ognl '@java.lang.System@out'

# 调用静态方法
ognl '@java.lang.System@getProperty("java.version")'

# 获取 Spring ApplicationContext
ognl '#context=@com.example.SpringContextUtil@getApplicationContext(),#context.getBean("userService").getUser(100)'

# 获取 Spring Bean
ognl '@springContext@getBean("userService")'

# 调用 Spring Bean 方法
ognl '#bean=@springContext@getBean("userService"),#bean.getUser(100)'

# 修改静态字段
ognl '#field=@com.example.config.AppConfig@TIMEOUT,#field.set(null,5000)'

# 查看 Map 内容
ognl '@springContext@getBean("userService").getCache()'

# 遍历集合
ognl '@springContext@getBean("userService").getUserList().iterator.{#this.id}'
# 获取静态字段值
ognl '@java.lang.System@out'

# 调用静态方法
ognl '@java.lang.System@getProperty("java.version")'

# 获取 Spring ApplicationContext
ognl '#context=@com.example.SpringContextUtil@getApplicationContext(),#context.getBean("userService").getUser(100)'

# 获取 Spring Bean
ognl '@springContext@getBean("userService")'

# 调用 Spring Bean 方法
ognl '#bean=@springContext@getBean("userService"),#bean.getUser(100)'

# 修改静态字段
ognl '#field=@com.example.config.AppConfig@TIMEOUT,#field.set(null,5000)'

# 查看 Map 内容
ognl '@springContext@getBean("userService").getCache()'

# 遍历集合
ognl '@springContext@getBean("userService").getUserList().iterator.{#this.id}'

获取 Spring Bean 的几种方式 ​

bash
# 方式 1:直接从已知的 SpringContextUtil 工具类获取
ognl '@com.example.util.SpringContextUtil@getApplicationContext().getBean("userService")'

# 方式 2:es 命令找到 ApplicationContext 的 classLoader
# 先找到持有 ApplicationContext 的类
sc -d org.springframework.context.ApplicationContext

# 方式 3:通过 Spring 的 ContextLoader
ognl '@org.springframework.web.context.ContextLoader@getCurrentWebApplicationContext().getBean("userService")'
# 方式 1:直接从已知的 SpringContextUtil 工具类获取
ognl '@com.example.util.SpringContextUtil@getApplicationContext().getBean("userService")'

# 方式 2:es 命令找到 ApplicationContext 的 classLoader
# 先找到持有 ApplicationContext 的类
sc -d org.springframework.context.ApplicationContext

# 方式 3:通过 Spring 的 ContextLoader
ognl '@org.springframework.web.context.ContextLoader@getCurrentWebApplicationContext().getBean("userService")'

修改日志级别 ​

bash
# 获取 LoggerContext
ognl '#lctx=@ch.qos.logback.classic.LoggerContext@getInstance(),#logger=#lctx.getLogger("com.example"),#logger.setLevel(@ch.qos.logback.classic.Level@DEBUG),#logger.getLevel()'

# Log4j2 修改日志级别
ognl '#ctx=@org.apache.logging.log4j.LogManager@getContext(),#cfg=#ctx.getConfiguration(),#loggerConfig=#cfg.getLoggerConfig("com.example"),#loggerConfig.setLevel(@org.apache.logging.log4j.Level@DEBUG),#ctx.updateLoggers()'
# 获取 LoggerContext
ognl '#lctx=@ch.qos.logback.classic.LoggerContext@getInstance(),#logger=#lctx.getLogger("com.example"),#logger.setLevel(@ch.qos.logback.classic.Level@DEBUG),#logger.getLevel()'

# Log4j2 修改日志级别
ognl '#ctx=@org.apache.logging.log4j.LogManager@getContext(),#cfg=#ctx.getConfiguration(),#loggerConfig=#cfg.getLoggerConfig("com.example"),#loggerConfig.setLevel(@org.apache.logging.log4j.Level@DEBUG),#ctx.updateLoggers()'

查看配置信息 ​

bash
# 查看系统属性
ognl '@java.lang.System@getProperties()'

# 查看环境变量
ognl '@java.lang.System@getenv()'

# 查看 Spring 配置
ognl '#env=@springContext@getBean("environment"),#env.getProperty("spring.datasource.url")'

# 查看某个 Bean 的属性
ognl '#bean=@springContext@getBean("dataSource"),#bean.getUrl()'
# 查看系统属性
ognl '@java.lang.System@getProperties()'

# 查看环境变量
ognl '@java.lang.System@getenv()'

# 查看 Spring 配置
ognl '#env=@springContext@getBean("environment"),#env.getProperty("spring.datasource.url")'

# 查看某个 Bean 的属性
ognl '#bean=@springContext@getBean("dataSource"),#bean.getUrl()'

安全约束:OGNL 可以执行任意代码,生产环境需通过 Tunnel Server 做权限控制。

vmtool(获取 JVM 对象) ​

vmtool 可以从 JVM 的堆中直接获取对象实例,不需要触发 GC。

bash
# 获取 Spring Bean
vmtool -x 3 --action getInstances --className org.springframework.context.ApplicationContext

# 获取所有 UserService 的实例
vmtool -x 2 --action getInstances --className com.example.service.UserService

# 获取指定条件的实例
vmtool -x 2 --action getInstances --className com.example.model.User --limit 10

# 获取实例并调用方法
vmtool -x 2 --action getInstances --className com.example.service.UserService --express 'instances[0].getUser(100)'

# 执行实例上的方法
vmtool -x 3 --action getInstances --className com.example.service.CacheService --express '#result=instances[0].getCacheMap()'
# 获取 Spring Bean
vmtool -x 3 --action getInstances --className org.springframework.context.ApplicationContext

# 获取所有 UserService 的实例
vmtool -x 2 --action getInstances --className com.example.service.UserService

# 获取指定条件的实例
vmtool -x 2 --action getInstances --className com.example.model.User --limit 10

# 获取实例并调用方法
vmtool -x 2 --action getInstances --className com.example.service.UserService --express 'instances[0].getUser(100)'

# 执行实例上的方法
vmtool -x 3 --action getInstances --className com.example.service.CacheService --express '#result=instances[0].getCacheMap()'

vmtool 与 ognl 的区别:

特性ognlvmtool
获取对象方式通过静态方法/字段引用直接在堆中搜索
需要已知入口是(需要 SpringContext 等)否
获取所有实例不支持支持
对堆的影响不影响可能触发 Full GC
性能开销小较大

heapdump(生成堆转储) ​

动态生成 heap dump 文件,与 jmap 功能类似但无需额外参数。

bash
# 生成 heap dump 到当前目录
heapdump

# 指定输出路径
heapdump /tmp/heap.hprof

# 只 dump live 对象(会触发 Full GC)
heapdump --live /tmp/heap.hprof

# 不触发 Full GC 的 dump
heapdump /tmp/heap.hprof --skip-live
# 生成 heap dump 到当前目录
heapdump

# 指定输出路径
heapdump /tmp/heap.hprof

# 只 dump live 对象(会触发 Full GC)
heapdump --live /tmp/heap.hprof

# 不触发 Full GC 的 dump
heapdump /tmp/heap.hprof --skip-live

搭配 MAT(Eclipse Memory Analyzer)使用:

bash
# 线上 dump
heapdump /tmp/heap-$(date +%Y%m%d%H%M%S).hprof

# 下载到本地,用 MAT 打开分析:
# - Suspects: 自动分析泄漏嫌疑
# - Dominator Tree: 查看大对象占用
# - OQL: 类 SQL 方式查询对象
# 线上 dump
heapdump /tmp/heap-$(date +%Y%m%d%H%M%S).hprof

# 下载到本地,用 MAT 打开分析:
# - Suspects: 自动分析泄漏嫌疑
# - Dominator Tree: 查看大对象占用
# - OQL: 类 SQL 方式查询对象

thread(线程分析) ​

查看当前 JVM 的线程信息,支持排查 CPU 高、死锁等问题。

bash
# 查看所有线程
thread

# 查看最忙的前 N 个线程(按 CPU 使用率排序)
thread -n 3

# 查看指定线程的栈
thread <thread-id>

# 查看 WAITING/BLOCKED 状态的线程
thread -b

# 死锁检测
thread --lock

# 查看指定状态的线程
thread --state WAITING

# 查看线程的 CPU 时间
thread -n 5 -i 1000
# 查看所有线程
thread

# 查看最忙的前 N 个线程(按 CPU 使用率排序)
thread -n 3

# 查看指定线程的栈
thread <thread-id>

# 查看 WAITING/BLOCKED 状态的线程
thread -b

# 死锁检测
thread --lock

# 查看指定状态的线程
thread --state WAITING

# 查看线程的 CPU 时间
thread -n 5 -i 1000

CPU 最高线程定位 ​

bash
# 找到 CPU 使用率最高的线程
thread -n 1

# 输出示例:
# "http-nio-8080-exec-10" Id=42 cpuUsage=85% ...
# 记下线程 ID 42

# 查看该线程的栈,确认代码位置
thread 42
# 找到 CPU 使用率最高的线程
thread -n 1

# 输出示例:
# "http-nio-8080-exec-10" Id=42 cpuUsage=85% ...
# 记下线程 ID 42

# 查看该线程的栈,确认代码位置
thread 42

死锁检测 ​

bash
$ thread -b
"Thread-1" Id=23 BLOCKED on java.util.HashMap@2aafb23c owned by "Thread-0" Id=22
    at java.util.HashMap.put(HashMap.java:612)
    -  locked java.util.HashMap@2aafb23c <--- 但该锁被 Thread-0 持有
$ thread -b
"Thread-1" Id=23 BLOCKED on java.util.HashMap@2aafb23c owned by "Thread-0" Id=22
    at java.util.HashMap.put(HashMap.java:612)
    -  locked java.util.HashMap@2aafb23c <--- 但该锁被 Thread-0 持有

profiler(火焰图) ​

Arthas 内置了 async-profiler,用于生成 CPU 或分配(Allocation)的火焰图。

bash
# 启动 CPU 采样
profiler start

# 查看采样状态
profiler status

# 停止并生成火焰图(SVG 格式)
profiler stop

# 指定输出文件
profiler stop --file /tmp/flamegraph.svg

# 指定采样事件
profiler start --event alloc

# 指定采样时长(秒)
profiler start --event cpu --duration 60

# 指定采样间隔(纳秒,默认 10_000_000 即 10ms)
profiler start --interval 1000000

# 生成不同格式
profiler stop --format html    # HTML 格式
profiler stop --format jfr     # JFR 格式(可用 JDK Mission Control 打开)

# 查看支持的采样事件
profiler list
# 启动 CPU 采样
profiler start

# 查看采样状态
profiler status

# 停止并生成火焰图(SVG 格式)
profiler stop

# 指定输出文件
profiler stop --file /tmp/flamegraph.svg

# 指定采样事件
profiler start --event alloc

# 指定采样时长(秒)
profiler start --event cpu --duration 60

# 指定采样间隔(纳秒,默认 10_000_000 即 10ms)
profiler start --interval 1000000

# 生成不同格式
profiler stop --format html    # HTML 格式
profiler stop --format jfr     # JFR 格式(可用 JDK Mission Control 打开)

# 查看支持的采样事件
profiler list

火焰图解读:

y 轴:调用栈深度
x 轴:采样比例(越宽说明 CPU 耗时越多)
颜色:无特殊含义,仅用于区分不同调用栈
y 轴:调用栈深度
x 轴:采样比例(越宽说明 CPU 耗时越多)
颜色:无特殊含义,仅用于区分不同调用栈

常见采样事件:

事件说明
cpuCPU 执行时间(默认)
alloc内存分配
lock锁竞争
wall墙钟时间(包括 I/O 等待)
itimerITimer 模式,开销更低但精度略差

生产环境使用建议:

bash
# 短时间采样(控制开销)
profiler start --event cpu --duration 30

# 生成火焰图
profiler stop --format html --file /tmp/cpu-report.html
# 短时间采样(控制开销)
profiler start --event cpu --duration 30

# 生成火焰图
profiler stop --format html --file /tmp/cpu-report.html

实战场景 ​

场景一:CPU 100% 定位 ​

现象:线上告警,某台机器 CPU 飙升到 100%。

排查步骤:

bash
# 1. 登录机器,找到 Java 进程 PID
top -c

# 2. 启动 Arthas
java -jar arthas-boot.jar <PID>

# 3. 找到 CPU 使用率最高的线程
thread -n 5

# 输出:
# "scheduler-1" Id=89 cpuUsage=92% ...
# "http-nio-8080-exec-2" Id=45 cpuUsage=3% ...

# 4. 查看最忙线程的栈
thread 89

# 5. 如果栈显示在某个方法,进一步用 trace 看内部调用
trace com.example.task.DataSyncTask syncData '#cost>100'

# 6. 确认问题:比如死循环、正则回溯、大量计算等
# 1. 登录机器,找到 Java 进程 PID
top -c

# 2. 启动 Arthas
java -jar arthas-boot.jar <PID>

# 3. 找到 CPU 使用率最高的线程
thread -n 5

# 输出:
# "scheduler-1" Id=89 cpuUsage=92% ...
# "http-nio-8080-exec-2" Id=45 cpuUsage=3% ...

# 4. 查看最忙线程的栈
thread 89

# 5. 如果栈显示在某个方法,进一步用 trace 看内部调用
trace com.example.task.DataSyncTask syncData '#cost>100'

# 6. 确认问题:比如死循环、正则回溯、大量计算等

快速定位总结:

  1. thread -n 5 找出最忙线程
  2. thread <id> 看栈定位代码
  3. trace 方法内部耗时,确认热点

场景二:OOM 排查 ​

现象:应用频繁 OOM,重启后一段时间又 OOM。

排查步骤:

bash
# 1. 启动 Arthas,查看内存使用
dashboard

# 发现 old gen 持续增长

# 2. dump 堆内存
heapdump --live /tmp/heap.hprof

# 3. 或用 vmtool 直接查看大对象
vmtool --action getInstances --className java.util.HashMap --limit 10 -x 3

# 4. 查看疑似泄漏的集合类
vmtool -x 5 --action getInstances --className java.util.ArrayList --express '{#size=instances[0].size(),#first=instances[0].get(0)}'

# 5. 在 MAT 中分析 heap dump:
#    - 找 Dominator Tree 中的大对象
#    - 找 GC Root 路径
#    - 常见的泄漏模式:ThreadLocal、静态集合、未关闭的连接
# 1. 启动 Arthas,查看内存使用
dashboard

# 发现 old gen 持续增长

# 2. dump 堆内存
heapdump --live /tmp/heap.hprof

# 3. 或用 vmtool 直接查看大对象
vmtool --action getInstances --className java.util.HashMap --limit 10 -x 3

# 4. 查看疑似泄漏的集合类
vmtool -x 5 --action getInstances --className java.util.ArrayList --express '{#size=instances[0].size(),#first=instances[0].get(0)}'

# 5. 在 MAT 中分析 heap dump:
#    - 找 Dominator Tree 中的大对象
#    - 找 GC Root 路径
#    - 常见的泄漏模式:ThreadLocal、静态集合、未关闭的连接

场景三:接口慢排查 ​

现象:用户反馈某个接口响应慢(比如超过 3 秒)。

排查步骤:

bash
# 1. 找到对应的 Controller 方法,用 trace 查看内部耗时
trace com.example.controller.OrderController createOrder '#cost>2000' -n 5

# 输出:
# `---[3200ms] OrderController.createOrder()
#     +---[10ms] OrderController.validateParams()
#     +---[2800ms] OrderController.callExternalService()  ← 这里最慢
#     |   `---[2790ms] HttpClient.execute()  ← HTTP 调用第三方接口
#     +---[50ms] OrderController.saveToDB()
#     `---[40ms] OrderController.formatResponse()

# 2. 进一步追踪外部调用
trace org.apache.http.client.HttpClient execute '#cost>1000'

# 3. 确认问题:第三方接口响应慢,考虑加超时、熔断、缓存
# 1. 找到对应的 Controller 方法,用 trace 查看内部耗时
trace com.example.controller.OrderController createOrder '#cost>2000' -n 5

# 输出:
# `---[3200ms] OrderController.createOrder()
#     +---[10ms] OrderController.validateParams()
#     +---[2800ms] OrderController.callExternalService()  ← 这里最慢
#     |   `---[2790ms] HttpClient.execute()  ← HTTP 调用第三方接口
#     +---[50ms] OrderController.saveToDB()
#     `---[40ms] OrderController.formatResponse()

# 2. 进一步追踪外部调用
trace org.apache.http.client.HttpClient execute '#cost>1000'

# 3. 确认问题:第三方接口响应慢,考虑加超时、熔断、缓存

场景四:日志打到一半查不到 ​

现象:线上出问题,但日志只打了 INFO 级别,看不到 DEBUG 日志。

解决:

bash
# 1. 用 ognl 动态修改日志级别(Logback 为例)
ognl '#lctx=@ch.qos.logback.classic.LoggerContext@getInstance(),#logger=#lctx.getLogger("com.example.biz"),#logger.setLevel(@ch.qos.logback.classic.Level@DEBUG),#logger.getLevel()'

# 2. 验证
ognl '#lctx=@ch.qos.logback.classic.LoggerContext@getInstance(),#logger=#lctx.getLogger("com.example.biz"),#logger.getLevel()'

# 3. 问题复现后,记得改回 INFO
ognl '#lctx=@ch.qos.logback.classic.LoggerContext@getInstance(),#logger=#lctx.getLogger("com.example.biz"),#logger.setLevel(@ch.qos.logback.classic.Level@INFO)'
# 1. 用 ognl 动态修改日志级别(Logback 为例)
ognl '#lctx=@ch.qos.logback.classic.LoggerContext@getInstance(),#logger=#lctx.getLogger("com.example.biz"),#logger.setLevel(@ch.qos.logback.classic.Level@DEBUG),#logger.getLevel()'

# 2. 验证
ognl '#lctx=@ch.qos.logback.classic.LoggerContext@getInstance(),#logger=#lctx.getLogger("com.example.biz"),#logger.getLevel()'

# 3. 问题复现后,记得改回 INFO
ognl '#lctx=@ch.qos.logback.classic.LoggerContext@getInstance(),#logger=#lctx.getLogger("com.example.biz"),#logger.setLevel(@ch.qos.logback.classic.Level@INFO)'

场景五:线上配置确认 ​

现象:需要确认线上某个配置项的取值,但不想去翻配置中心。

解决:

bash
# 1. 获取 Spring Environment
ognl '#env=@com.example.util.SpringContextUtil@getApplicationContext().getEnvironment(),#env.getProperty("myapp.timeout")'

# 2. 查看某个 Bean 的属性
ognl '@springContext@getBean("dataSource").getUrl()'

# 3. 查看配置类的字段
ognl '@com.example.config.AppConfig@TIMEOUT'
# 1. 获取 Spring Environment
ognl '#env=@com.example.util.SpringContextUtil@getApplicationContext().getEnvironment(),#env.getProperty("myapp.timeout")'

# 2. 查看某个 Bean 的属性
ognl '@springContext@getBean("dataSource").getUrl()'

# 3. 查看配置类的字段
ognl '@com.example.config.AppConfig@TIMEOUT'

场景六:热修复 ​

现象:线上有一个紧急 bug,等不及发版,需要立刻修复。

流程:

bash
# 1. 反编译线上 class 到本地
jad --source-only com.example.biz.PaymentService > /tmp/PaymentService.java

# 2. 修改源码(比如修复空指针)
# 编辑 /tmp/PaymentService.java,添加空判断

# 3. 编译
mc /tmp/PaymentService.java -d /tmp/output

# 4. 热替换
redefine /tmp/output/com/example/biz/PaymentService.class

# 5. 验证
jad com.example.biz.PaymentService

# 6. 用 watch 验证修复后的行为
watch com.example.biz.PaymentService pay "{params,returnObj}"
# 1. 反编译线上 class 到本地
jad --source-only com.example.biz.PaymentService > /tmp/PaymentService.java

# 2. 修改源码(比如修复空指针)
# 编辑 /tmp/PaymentService.java,添加空判断

# 3. 编译
mc /tmp/PaymentService.java -d /tmp/output

# 4. 热替换
redefine /tmp/output/com/example/biz/PaymentService.class

# 5. 验证
jad com.example.biz.PaymentService

# 6. 用 watch 验证修复后的行为
watch com.example.biz.PaymentService pay "{params,returnObj}"

热修复约束:

  • 不能修改类的结构(不能增减字段/方法)。
  • 修改的方法正在执行时,替换不会影响当前执行的方法实例,新调用会使用新代码。
  • JVM 重启后热替换失效,需要尽快发正式版本。
  • 建议在修复后做好记录,避免忘记回正。

最佳实践 ​

生产环境使用注意事项 ​

1. 权限控制 ​

  • 生产环境使用 Arthas 需要审批流程。
  • 建议通过 Tunnel Server 统一管理,记录操作审计日志。
  • 限制能够 attach 的用户和进程。

2. 性能影响 ​

  • watch、trace、tt 都有性能开销,高并发接口慎重使用。
  • 优先使用条件表达式缩小范围(如 #cost>100、params[0]==100)。
  • 观测完毕后及时 Ctrl+C 退出或使用 -n 限制次数。
  • profiler 采样建议控制在 30-60 秒,避免长时间采集。
  • vmtool 的 getInstances 可能触发 GC,谨慎使用。

3. 安全问题 ​

  • OGNL 可以执行任意代码,默认可达性极强。
  • redefine 可能破坏类的结构,导致 JVM 不稳定。
  • 生产环境建议禁用或审计 ognl 和 redefine 命令。

4. 操作规范 ​

  • 操作前记录当前状态(如 quick start 前的 dashboard 截图)。
  • 操作完一个命令后及时确认结果,再执行下一步。
  • 热替换后务必用 jad 验证,并记录替换内容。
  • 修改日志级别后记得改回原级别。

权限控制——Tunnel Server ​

Tunnel Server 是 Arthas 的远程管理组件,提供统一接入和管理能力。

部署架构:

App 1 ──arthas agent──┐
                      ├── Tunnel Server ── Web Console
App 2 ──arthas agent──┘
App 1 ──arthas agent──┐
                      ├── Tunnel Server ── Web Console
App 2 ──arthas agent──┘

服务端部署:

bash
# 下载 tunnel-server
wget https://arthas.aliyun.com/arthas-tunnel-server.jar

# 启动
java -jar arthas-tunnel-server.jar

# 默认端口:HTTP 8080,WebSocket 7777
# 下载 tunnel-server
wget https://arthas.aliyun.com/arthas-tunnel-server.jar

# 启动
java -jar arthas-tunnel-server.jar

# 默认端口:HTTP 8080,WebSocket 7777

客户端连接:

bash
# 启动时指定 tunnel server
java -jar arthas-boot.jar <PID> --tunnel-server ws://tunnel-server:7777/ws --agent-id my-app-001

# 或在 as.sh 中指定
./as.sh <PID> --tunnel-server http://tunnel-server:7777 --agent-id my-app-001
# 启动时指定 tunnel server
java -jar arthas-boot.jar <PID> --tunnel-server ws://tunnel-server:7777/ws --agent-id my-app-001

# 或在 as.sh 中指定
./as.sh <PID> --tunnel-server http://tunnel-server:7777 --agent-id my-app-001

安全管理:

  • Tunnel Server 支持自定义认证和授权。
  • 建议配置 HTTPS 和 Basic Auth。
  • 操作日志可以对接 ELK 实现审计。
yaml
# tunnel-server 的 application.yml
arthas:
  tunnel-server:
    # 启用认证
    auth-enabled: true
    # 白名单用户
    auth-users:
      - admin
      - ops
# tunnel-server 的 application.yml
arthas:
  tunnel-server:
    # 启用认证
    auth-enabled: true
    # 白名单用户
    auth-users:
      - admin
      - ops

与 Prometheus / Grafana 配合 ​

Arthas 不直接替代 APM 监控,但可以作为 APM 发现异常后的二次定位工具。

推荐工作流:

Grafana 告警(CPU 高/接口慢)
            │
            ▼
确认问题应用与实例
            │
            ▼
Arthas 接入(thread -n / trace / profiler)
            │
            ▼
定位根因代码
            │
            ▼
热修复 或 提 PR 发版
Grafana 告警(CPU 高/接口慢)
            │
            ▼
确认问题应用与实例
            │
            ▼
Arthas 接入(thread -n / trace / profiler)
            │
            ▼
定位根因代码
            │
            ▼
热修复 或 提 PR 发版

配合方式:

场景Prometheus/Grafana 发现Arthas 深入定位
CPU 高告警规则触发,CPU 指标异常thread -n 找线程,profiler 出火焰图
接口响应慢接口 P99 延迟上升trace 逐级定位耗时节点
OOMJVM memory 持续增长heapdump + MAT 或 vmtool 查大对象
错误率上升接口 error rate 飙升watch -e 看异常详情

总结 ​

Arthas 的核心价值在于:线上问题,现场排查,不改代码,不重启。

能力对应命令一句话总结
看类信息sc / sm搜索已加载的类和方法
看源码jad反编译确认线上代码版本
观测方法watch看入参/返回值/异常/耗时
追踪链路trace方法内逐级耗时分布
调用栈stack这个方法是谁调用的
记录回放tt录下来,回头慢慢分析
执行表达式ognl最强大,什么都能干
取对象vmtool直接从堆里取对象
线程分析threadCPU 最高线程/死锁检测
堆转储heapdump动态 dump 堆内存
火焰图profilerCPU/内存分配热点采样
热替换mc + redefine不重启修 Bug

掌握以上命令,就能应对绝大多数线上 Java 问题排查场景。建议在日常开发中多练习,熟能生巧,关键时刻才能快速定位问题。