当前位置: 首页 > news >正文

使用Arthas MCP对Java应用进行线上诊断实践

在实际的 Java 应用运维中,我们经常遇到以下问题:

  • CPU 飙高:某个线程死循环导致服务器负载过高
  • 内存泄漏:对象无法被回收,最终导致 OOM
  • 死锁:多个线程互相等待,系统挂起
  • 线程池满:任务堆积,新任务无法执行
  • 慢方法:某些接口响应时间过长
  • 异常被吞:抛出异常但未记录,难以排查

这些问题在本地开发时容易调试,但在生产环境中,我们通常只能通过日志和监控来推测。Arthas 是阿里巴巴开源的 Java 诊断工具,可以帮助我们在线上环境快速定位问题。

传统诊断方式下,我们需要登录到目标环境,并通过Attach 到目标进程来进行线上诊断

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jararithas> thread -b  # 检测死锁
arithas> dashboard  # 查看系统概况
arithas> trace com.example.MyClass myMethod  # 追踪方法调用

本次使用的版本为Arthas 4.1.9,并通过java agent的方式随应用自动启动

# javaagent 方式
$ java -javaagent:arthas-agent.jar -jar app.jar

近期arthas新增了原生的MCP功能,让我们能够更加灵活地结合AI agent进行智能诊断。本文的核心目标为构建一个全自动的 Java 应用诊断系统。让AI Agent 能够理解问题并选择合适的诊断工具,生成结构化的诊断报告,包含问题定位、根因分析、解决建议。

MCP 是一个开放协议,用于连接 AI 应用和外部工具(如数据库、API、文件系统等)。它定义了标准化的接口,让 AI Agent 可以通过统一的 API 调用各种工具。此外,本次的agent框架使用Strands Agent

# 直接获得结构化数据
tools = await session.list_tools()
result = await session.call_tool("thread", arguments={"arguments": ["-b"]})

整体的逻辑结构如下

  • Python 负责进程启停管理、等待 Arthas 服务就绪、创建智能代理、调度测试任务;
  • AI Agent 层基于本地通义千问编码大模型,以诊断专家角色定位,依托 MCP 工具集调用能力,按理解任务 - 选用工具 - 分析数据 - 输出结论流程完成智能诊断;
  • MCP 客户端层通过 HTTP/SSE 协议通信,配置令牌鉴权,维持长连接会话,实现与服务端的标准协议交互;
  • Arthas MCP 服务层监听 9999 端口提供 MCP 接口,封装线程、JVM、内存、链路追踪、反编译等 Arthas 诊断工具能力;
  • 应用层预置 CPU 高占用、死锁、内存泄漏、异常、线程池耗尽、慢方法共 6 类故障测试场景,作为系统诊断的目标被测应用。

默认配置下,Arthas 会监听 127.0.0.1,导致外部无法连接。因此需要创建配置文件,明确指定监听地址和端口

# 配置文件路径
~/.arthas/lib/4.1.9/arthas/arthas.properties
# MCP Endpoint 配置
arthas.mcpEndpoint=/mcp# HTTP Server 配置
arthas.ip=0.0.0.0          # 绑定所有网卡,允许远程访问
arthas.httpPort=9999        # HTTP API 端口
arthas.telnetPort=0         # 禁用 Telnet(0 表示禁用)# 认证配置
arthas.password=S2TotROqw5zmT8po7m7aiifdZxUzReSrknDslxe5tIBjMWlN5yrpatPIJogSAX0v

验证 Arthas MCP Server

$ java -javaagent:~/.arthas/lib/4.1.9/arthas/arthas-agent.jar -Xmx256m -cp out <java-class> # 测试 MCP Server,上个命令启动后会自动输出Bearer认证token
$ curl -H "Authorization: Bearer S2TotROqw5zmT8po7m7aiifdZxUzReSrknDslxe5tIBjMWlN5yrpatPIJogSAX0v" \http://localhost:9999/mcp# 返回 SSE 流
event: endpoint
data: {"method": "initialize", ...}

系统提示词结构

GENERIC_SYSTEM_PROMPT = """你是一个资深的 Java 应用诊断专家,拥有 10 年以上的生产环境排查经验。## 当前诊断目标
{class_name} - {desc}## 诊断原则
1. **观察驱动**:首先观察系统的整体状态(JVM、线程、内存),形成初步假设
2. **工具优先级**:- 简单查询工具优先(thread、jvm、memory、dashboard)- 如需流式工具(trace、monitor、watch),注意可能受认证限制- 使用 jad 反编译源码来理解代码逻辑
3. **多角度验证**:结合多个工具的结果交叉验证,避免误判
4. **根因导向**:不只找到现象,要深入分析根本原因
5. **解决建议**:给出可操作的解决方案## Arthas 工具速查
- `thread` / `thread -n 3` - 查看线程列表/CPU 最高线程
- `thread -b` - 检测死锁
- `thread <id>` - 查看特定线程的详细堆栈
- `jvm` - JVM 运行时信息
- `memory` - 内存使用情况
- `dashboard` / `dashboard -i 2000` - 系统概况/定期监控
- `jad <ClassName>` - 反编译类代码
- `sysprop` - 系统属性
- `logger` - 日志配置## 认证说明
注意:部分流式工具(trace、monitor、watch)可能需要额外的会话认证。
如果遇到 401 错误,请改用其他工具组合(如 thread + jad)来达到同样的诊断目标。## 输出要求
请按照以下格式输出诊断结果:
1. **观察发现**:通过工具看到的异常现象
2. **问题定位**:问题的具体位置(类名、方法名、行号)
3. **根因分析**:为什么会出现这个问题
4. **解决方案**:具体的修复建议开始诊断吧!
"""

strands agent构造如下

# 3. 创建 MCP Client 和 Agent(使用通用系统提示词)
arthas_mcp = MCPClient(lambda: streamablehttp_client(url="http://localhost:9999/mcp",headers={"Authorization": "Bearer S2TotROqw5zmTxxxxxpatPIJogSAX0v"})
)# 构建通用系统提示词
system_prompt = GENERIC_SYSTEM_PROMPT.format(class_name=class_name,desc=scenario['desc']
)agent = Agent(model=model,system_prompt=system_prompt,tools=[arthas_mcp], # 将mcp注册到agent中
)

CPU 高利用率

用户报告:某个 Java 进程 CPU 使用率持续在 100% 左右,系统响应缓慢,影响其他服务正常运行。

  • CPU 使用率持续高位(100%)
  • 系统响应缓慢

模拟的故障代码

public class CpuHighDemo {public static void main(String[] args) throws Exception {System.out.println("PID: " + ProcessHandle.current().pid());System.out.println("[CpuHighDemo] CPU飙高场景启动...");// 创建一个死循环线程,执行密集计算new Thread(() -> {while (true) {// 密集计算操作(幂运算)Math.pow(Math.random(), Math.random());}}, "cpu-busy-thread").start();  // ← 关键:线程名称Thread.currentThread().join();}
}

启动进程

启动命令: java -javaagent:/home/ec2-user/.arthas/lib/4.1.9/arthas/arthas-agent.jar -Xmx256m -cp /home/ec2-user/workspace/java-diagnosis-demo/out CpuHighDemo
进程启动 PID: 238433
等待 Arthas MCP Server 启动...
✓ Arthas MCP Server 已就绪 (端口 9999)

测试日志

Tool #1: dashboardTool #2: threadTool #3: jadTool #4: thread

诊断结果

### 1. 观察发现
通过使用 `dashboard` 和 `thread -n 5` 命令观察到以下异常现象:
- 线程 `cpu-busy-thread` (ID 27) 占用了高达 99.97% 的 CPU 使用率。
- 该线程处于 RUNNABLE 状态,表明它正在执行任务且没有等待或阻塞。
- 其他线程的 CPU 使用率都很低,几乎可以忽略不计。### 2. 问题定位
问题的具体位置在 `CpuHighDemo` 类中,具体是第 12 行的代码:
```java
Math.pow(Math.random(), Math.random());这个无限循环的操作导致了 CPU 使用率飙升。### 3. 根因分析从反编译的代码可以看出,在主线程启动后创建了一个名为 `cpu-busy-thread` 的新线程。此线程执行一个无限循环,不断地调用 `Math.pow(Math.random(), Math.random())` 方法。由于这是一个计算密集型操作,并且在无限循环中不断执行,因此会导致该线程持续占用大量的 CPU 资源,从而使得整个进程的 CPU 使用率达到接近 100% 的水平。### 4. 解决方案要解决这个问题,需要根据业务需求调整代码逻辑:
1. **如果是测试场景**:可以在循环中添加适当的休眠时间,例如使用 `Thread.sleep(1)` 来降低CPU利用率。
2. **如果是生产环境**:应该检查是否有类似的死循环或者计算密集型任务未加控制地运行。
3. **长期优化**:对于长时间运行的任务,考虑将其放入线程池中执行,并设置合理的并发度以避免过度消耗系统资源。

死锁

用户报告程序突然挂起,不再处理任何请求,日志也没有新的输出。重启后恢复,但几分钟后又挂起。

  • 程序挂起,无响应
  • 没有错误日志

代码实现

public class DeadlockDemo {private static final Object lockA = new Object();private static final Object lockB = new Object();public static void main(String[] args) throws Exception {System.out.println("PID: " + ProcessHandle.current().pid());System.out.println("[DeadlockDemo] 死锁场景启动...");// 线程 1:先获取 lockA,再等待 lockBnew Thread(() -> {synchronized (lockA) {              // ← 获取 lockAtry { Thread.sleep(100); } catch (InterruptedException e) {}System.out.println("[Thread-1] 已获取 lockA,等待 lockB...");synchronized (lockB) {          // ← 等待 lockB(被 Thread-2 持有)System.out.println("[Thread-1] 已获取 lockB");  // 永远不会执行}}}, "deadlock-thread-1").start();// 线程 2:先获取 lockB,再等待 lockAnew Thread(() -> {synchronized (lockB) {              // ← 获取 lockBtry { Thread.sleep(100); } catch (InterruptedException e) {}System.out.println("[Thread-2] 已获取 lockB,等待 lockA...");synchronized (lockA) {          // ← 等待 lockA(被 Thread-1 持有)System.out.println("[Thread-2] 已获取 lockA");  // 永远不会执行}}}, "deadlock-thread-2").start();Thread.currentThread().join();}
}

测试日志

Agent 开始自主诊断...
Tool #1: thread
Tool #2: thread
Tool #3: jad
Tool #4: jvm
Tool #5: memory
Tool #6: sysprop

完整诊断报告

### 1. 观察发现通过使用 `thread -b` 命令检测到系统中存在死锁:
- 线程 `deadlock-thread-2` (ID: 28) 被阻塞,等待获取锁 `java.lang.Object@26d694600`,该锁被线程 `deadlock-thread-1` (ID: 27) 持有。
- 线程 `deadlock-thread-1` (ID: 27) 被阻塞,等待获取锁 `java.lang.Object@26d65717`,该锁被线程 `deadlock-thread-2` (ID: 28) 持有。这形成了一个典型的循环依赖死锁。
进一步的 JVM 信息确认了这一点:
- `jvm` 命令输出显示 `DEADLOCK-COUNT` 为 2,证实了存在两个线程参与了死锁。### 2. 问题定位
通过对 `DeadlockDemo` 类进行反编译 (`jad` 命令),我们找到了问题的具体位置:
- **类名**: `DeadlockDemo`
- **涉及对象**:- `lockA` (在代码第10行定义)- `lockB` (在代码第11行定义)
- **线程1** (`deadlock-thread-1`):- 获取了 `lockA` (第14行)- 尝试获取 `lockB` (第16行) 时被阻塞
- **线程2** (`deadlock-thread-2`):- 获取了 `lockB` (第21行)- 尝试获取 `lockA` (第23行) 时被阻塞### 3. 根因分析
出现死锁的根本原因是两个线程以不同的顺序获取相同的两把锁:
1. 线程1首先获取 `lockA`,然后尝试获取 `lockB`。
2. 线程2首先获取 `lockB`,然后尝试获取 `lockA`。
由于两个线程都在等待对方释放自己所需的锁,而它们又都不释放自己已经持有的锁,因此造成了永久性的阻塞,即死锁。
这种经典的"交叉锁"问题是并发编程中最常见的死锁形式之一。### 4. 解决方案
为了避免此类死锁的发生,可以采取以下几种策略:
#### 方案一:统一加锁顺序
确保所有线程都以相同的顺序获取锁。例如,在访问这两个资源时,总是先获取 `lockA` 再获取 `lockB`。
#### 方案二:使用超时机制
使用 `java.util.concurrent.locks.ReentrantLock` 的 `tryLock(long timeout, TimeUnit unit)` 方法。
#### 方案三:使用高级并发工具
考虑使用 `java.util.concurrent` 包中的高级同步工具,如 `Semaphore`、`CountDownLatch` 或者 `BlockingQueue`。

慢方法

用户报告:某个业务接口响应时间过长(>1秒),影响用户体验。监控显示该接口平均响应时间持续在 1000ms 以上。

代码实现

public class SlowMethodDemo {public static void main(String[] args) throws Exception {System.out.println("PID: " + ProcessHandle.current().pid());System.out.println("[SlowMethodDemo] 慢方法场景启动...");new Thread(() -> {while (true) {doSlowBusiness();  // ← 慢方法try { Thread.sleep(1000); } catch (InterruptedException e) { break; }}}, "slow-method-thread").start();Thread.currentThread().join();}// 慢方法:总耗时约 1 秒static void doSlowBusiness() {step1();  // 200msstep2();  // 500msstep3();  // 300ms}static void step1() {try { Thread.sleep(200); } catch (InterruptedException e) {}}static void step2() {try { Thread.sleep(500); } catch (InterruptedException e) {}}static void step3() {try { Thread.sleep(300); } catch (InterruptedException e) {}}
}

测试日志

Tool #1: threadTool #2: jvmTool #3: memoryTool #4: dashboardTool #5: scTool #6: smTool #7: jadTool #8: threadTool #9: thread

完整诊断报告

### 1. 观察发现
通过一系列工具的综合分析,我们发现了以下关键信息:- **线程状态**: `slow-method-thread` 线程处于 `TIMED_WAITING` 状态,表明它正在执行一个定时等待操作。
- **CPU 使用**: 整体系统负载不高,没有明显的CPU瓶颈。
- **内存使用**: JVM 内存使用正常,GC活动较少,没有出现频繁Full GC的情况。
- **代码结构**: 通过反编译 `SlowMethodDemo` 类,我们看到了其主要逻辑结构,特别是 `doSlowBusiness()` 方法及其调用的三个步骤方法。### 2. 问题定位
具体的问题出现在 `SlowMethodDemo` 类的 `step2()` 方法中。
- **类名**: `SlowMethodDemo`
- **方法名**: `step2`
- **行号**: 第27行 (`Thread.sleep(500L);`)### 3. 根因分析
通过对 `SlowMethodDemo` 的源码分析,我们发现该类模拟了一个慢速业务处理流程:
```java
static void doSlowBusiness() {SlowMethodDemo.step1(); // 耗时200msSlowMethodDemo.step2(); // 耗时500msSlowMethodDemo.step3(); // 耗时300ms
}其中,`step2()` 方法包含了长达500毫秒的睡眠操作:```java
static void step2() {try {Thread.sleep(500L);} catch (InterruptedException e) {// empty catch block}
}
```
这个人为添加的延迟是导致整个业务接口响应时间超过1秒的主要原因。虽然这不是真实业务场景中的性能问题,但确实反映了在开发或测试环境中如何模拟慢速服务的一种常见做法。### 4. 解决方案针对这种情况,可以采取以下几种措施:
1. **如果是测试环境**: 可以根据需要调整延迟时间或者移除不必要的延迟。可以使用配置项来控制这些延迟。
2. **如果是生产环境问题**: 需要进一步分析 `step2()` 中实际执行的业务逻辑,找出真正的性能瓶颈点。
3. **通用优化建议**:- 将耗时操作分解为多个小任务并行处理- 引入缓存机制减少重复计算- 使用连接池管理数据库和网络连接- 定期进行性能压测和代码审查

参考资料

  • https://arthas.aliyun.com/doc/agent.html
  • https://arthas.aliyun.com/doc/mcp-server.html
http://www.jsqmd.com/news/805097/

相关文章:

  • CST 2022学生版实战:手把手教你设计一个6GHz的Wi-Fi 6E矩形贴片天线
  • 告别安卓模拟器!3分钟学会在Windows上直接安装APK应用
  • 厚街吊车租赁哪家值得推荐:秒杀吊车租赁服务优质 - 17322238651
  • 从游戏开发到算法竞赛:三角形面积公式的跨界应用与Python实现
  • 2025最权威的六大AI学术网站推荐
  • 工业盘式扭矩传感器优质品牌哪家靠谱?广东犸力稳居品牌排行推荐首选 - 品牌速递
  • C++数据结构进阶|并查集(Union-Find)详解:从原理到面试实战
  • Koikatu HF Patch终极指南:5步解锁完整游戏体验与200+增强功能
  • AI智能体赋能投行级财务分析:四大模型实战与OpenClaw集成指南
  • PixelAnnotationTool完整指南:5分钟掌握智能图像标注技巧
  • Visual C++运行库一键修复:告别“应用程序无法启动“的终极解决方案
  • 音响系统维护维保
  • 从五管OTA到两级运放:在Cadence IC617中如何用gm/id法平衡性能、面积与功耗?
  • 在macOS上打造完美音乐伴侣:LyricsX歌词工具深度体验指南
  • 终极歌词同步神器:5分钟打造你的macOS专属音乐伴侣 [特殊字符]
  • 测功机专用扭矩传感器品牌怎么选靠谱?广东犸力专业厂家值得长期信赖 - 品牌速递
  • C++数据结构进阶|图(Graph)详解:从存储到面试高频算法实战
  • Google Gemini AI 资源导航:从入门到精通的开发者指南
  • Sled:语音远程控制本地AI编程助手的实现与部署
  • 终极Windows窗口调整工具:WindowResizer完全使用指南
  • 芯片设计如何打造更稳定的高温老化座?
  • SQL 多表联查从入门到精通,INNER/LEFT/RIGHT/FULL JOIN 一篇吃透
  • 时序数据库市场格局生变:TDengine 与 InfluxDB 的差异化竞争
  • PiliPlus跨平台B站客户端:开源免费的全平台观影解决方案
  • 2025届必备的六大AI写作网站实测分析
  • 什么是操作系统?操作系统都有哪些?如何使用操作系统?
  • 别被忽悠了!AI 根本没降低程序员门槛,反而把门槛抬到了历史最高
  • 告别网页卡顿!用PotPlayer+DPL列表,打造你的专属B站/斗鱼/虎牙直播聚合中心
  • KMS_VL_ALL_AIO智能激活脚本使用指南
  • 贵州铝合金门窗一线品牌推荐:绿色科技赋能家居安全新高度 - 品牌策略师