Arthas 在线诊断
阿里巴巴 Java 诊断工具,生产环境问题排查利器。 Arthas(阿尔萨斯)是 Alibaba 开源的 Java 诊断工具,支持在线上环境实时诊断,无需修改代码或重启应用,让开发者能够以极低的代价定位生产环境问题。
目录
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 启动(推荐)
# 下载 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)
# 一键安装并启动
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:7777Windows 启动
# 下载 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 中添加依赖:
<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 配置:
arthas:
agent-id: my-app-001
tunnel-server: ws://127.0.0.1:7777/ws
app-name: my-apparthas:
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> | 输出行数限制 |
示例:
java -jar arthas-boot.jar <PID> --http-port 8563 --session-timeout 3600java -jar arthas-boot.jar <PID> --http-port 8563 --session-timeout 3600非交互式执行命令
Arthas 支持批量模式(Batch Mode),一次执行多条命令后退出:
# 执行单条命令
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.txtcommands.txt 示例:
sc -d com.example.service.UserService
watch com.example.service.UserService getUser "{params,returnObj}" -x 2sc -d com.example.service.UserService
watch com.example.service.UserService getUser "{params,returnObj}" -x 2Dashboard
Dashboard 是 Arthas 的实时仪表盘,输入 dashboard 即可查看当前 JVM 的概要信息。
# 查看 dashboard,默认每 5 秒刷新
dashboard
# 只输出一次,不持续刷新
dashboard -n 1
# 指定刷新间隔(秒)
dashboard -i 2000# 查看 dashboard,默认每 5 秒刷新
dashboard
# 只输出一次,不持续刷新
dashboard -n 1
# 指定刷新间隔(秒)
dashboard -i 2000Dashboard 输出包含以下几个关键区域:
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) = 2480gc.ps_scavenge.count = 128
gc.ps_scavenge.time(ms) = 8960
gc.ps_marksweep.count = 4
gc.ps_marksweep.time(ms) = 24804. 运行时信息
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 148Runtime
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 命令。
# 查看类的详细信息
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 |
输出示例:
$ 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 5674cd4dsm(Search Method)
搜索已加载类的方法。
# 查看类的所有方法
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(反编译)
反编译已加载的类,用于验证线上代码是否与源码一致。
# 反编译整个类
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典型场景:
# 排查:线上代码是否已包含某次修改?
jad com.example.service.UserService
# 输出源码中是否有预期的逻辑
# 如果看到的是旧代码,说明发布未生效# 排查:线上代码是否已包含某次修改?
jad com.example.service.UserService
# 输出源码中是否有预期的逻辑
# 如果看到的是旧代码,说明发布未生效输出示例:
$ 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。
# 第一步:修改源码后,编译
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 重启后丢失。
典型流程:
# 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.UserServicedump(dump class 到本地)
将 JVM 中已加载的 class 文件 dump 到本地磁盘。
# 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 最常用的命令之一。
# 观察方法返回值
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) |
实用示例:
# 线上排查:看某个方法返回了什么奇怪的值
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 2trace(方法内部调用链路 + 耗时)
trace 是排查接口慢的利器——它会输出方法内部所有子调用的耗时分布。
# 追踪方法的调用链路
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'输出示例:
$ 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 的性能开销较大,生产环境建议配合条件表达式使用,避免大流量下开启。
常见用法场景:
# 只追踪特定入参的慢调用
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(方法调用栈)
查看当前方法的调用栈,用于回答"这个方法被谁调用了"。
# 查看方法的调用栈
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输出示例:
$ 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 命令记录方法的每次调用,并支持事后回放。
# 记录方法的每次调用
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 | 清空所有记录 |
典型场景:
# 第一次记录
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 秒一个周期)。
# 统计方法调用
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"输出示例:
$ 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 表达式。
# 获取静态字段值
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 的几种方式
# 方式 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")'修改日志级别
# 获取 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()'查看配置信息
# 查看系统属性
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。
# 获取 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 的区别:
| 特性 | ognl | vmtool |
|---|---|---|
| 获取对象方式 | 通过静态方法/字段引用 | 直接在堆中搜索 |
| 需要已知入口 | 是(需要 SpringContext 等) | 否 |
| 获取所有实例 | 不支持 | 支持 |
| 对堆的影响 | 不影响 | 可能触发 Full GC |
| 性能开销 | 小 | 较大 |
heapdump(生成堆转储)
动态生成 heap dump 文件,与 jmap 功能类似但无需额外参数。
# 生成 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)使用:
# 线上 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 高、死锁等问题。
# 查看所有线程
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 1000CPU 最高线程定位
# 找到 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死锁检测
$ 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)的火焰图。
# 启动 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 耗时越多)
颜色:无特殊含义,仅用于区分不同调用栈常见采样事件:
| 事件 | 说明 |
|---|---|
cpu | CPU 执行时间(默认) |
alloc | 内存分配 |
lock | 锁竞争 |
wall | 墙钟时间(包括 I/O 等待) |
itimer | ITimer 模式,开销更低但精度略差 |
生产环境使用建议:
# 短时间采样(控制开销)
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%。
排查步骤:
# 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. 确认问题:比如死循环、正则回溯、大量计算等快速定位总结:
thread -n 5找出最忙线程thread <id>看栈定位代码trace方法内部耗时,确认热点
场景二:OOM 排查
现象:应用频繁 OOM,重启后一段时间又 OOM。
排查步骤:
# 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 秒)。
排查步骤:
# 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 日志。
解决:
# 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)'场景五:线上配置确认
现象:需要确认线上某个配置项的取值,但不想去翻配置中心。
解决:
# 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,等不及发版,需要立刻修复。
流程:
# 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──┘服务端部署:
# 下载 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客户端连接:
# 启动时指定 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 实现审计。
# 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 逐级定位耗时节点 |
| OOM | JVM memory 持续增长 | heapdump + MAT 或 vmtool 查大对象 |
| 错误率上升 | 接口 error rate 飙升 | watch -e 看异常详情 |
总结
Arthas 的核心价值在于:线上问题,现场排查,不改代码,不重启。
| 能力 | 对应命令 | 一句话总结 |
|---|---|---|
| 看类信息 | sc / sm | 搜索已加载的类和方法 |
| 看源码 | jad | 反编译确认线上代码版本 |
| 观测方法 | watch | 看入参/返回值/异常/耗时 |
| 追踪链路 | trace | 方法内逐级耗时分布 |
| 调用栈 | stack | 这个方法是谁调用的 |
| 记录回放 | tt | 录下来,回头慢慢分析 |
| 执行表达式 | ognl | 最强大,什么都能干 |
| 取对象 | vmtool | 直接从堆里取对象 |
| 线程分析 | thread | CPU 最高线程/死锁检测 |
| 堆转储 | heapdump | 动态 dump 堆内存 |
| 火焰图 | profiler | CPU/内存分配热点采样 |
| 热替换 | mc + redefine | 不重启修 Bug |
掌握以上命令,就能应对绝大多数线上 Java 问题排查场景。建议在日常开发中多练习,熟能生巧,关键时刻才能快速定位问题。
Cao Note