Java 性能分析在软件开发中是一个老生常谈的话题。GC 日志怎么看、线程转储怎么分析、堆转储怎么排查,这些能力被认为是一个资深 Java 开发者的基本素养。但如果冷静审视这个领域的现状,你会发现存在三个系统性的断层。
1.1 传统 Java 性能分析的三个断层
1.1.1 工具割裂
一个典型的生产问题排查路径是这样的:
线上出现告警------响应率下降、接口 P99 延迟升高、OOM 重启------工程师首先打开的是监控系统。Grafana 面板上,CPU 曲线、堆内存曲线、GC 频率曲线、线程数曲线一目了然。
从监控中你看到了什么?
- 堆内存呈锯齿状上升,每次 GC 后不回落到基线 → 怀疑内存泄漏 → 需要取 GC 日志确认 → 进一步可能导堆转储
- GC 频率突然升高且 STW 时间变长 → 怀疑 GC 参数不合适或堆压力过大 → 需要详细 GC 日志分析
- 线程数持续上涨 → 怀疑线程泄漏 → 需要导 jstack 看线程状态分布
- CPU 跑满但堆正常 → 怀疑死循环或热点方法 → 需要看线程转储 RUNNABLE 状态
当前市场上并不缺少 APM 工具------Datadog、New Relic、SkyWalking、Pinpoint 等------它们能够提供从监控到告警的完整链路,在规模化系统中几乎已成标配。但 APM 的部署和维护成本较高,通常需要 Agent 接入、数据传输通道、后端存储集群,对小团队或个人开发者而言过于沉重。对于他们来说,更常用的是本地 CLI 工具(jstat、jstack、jmap)或轻量级的在线分析工具,随用随走,不需要搭建一整套观测体系。
现有的 Java 诊断工具有不少,但各自都有局限:
| 工具 | 定位 | 问题 |
|---|---|---|
| GCViewer | 桌面端 GC 日志可视化 | 开源项目已停更多年,不支持 JDK 17+ 的新格式和 ZGC/Shenandoah |
| GCEasy | 在线 GC 日志分析 | 做了基本的 AI 诊断规则,但只处理单次上传、单份文件,无法多轮对话和交叉分析 |
| FastThread | 在线线程转储分析 | 同上,一次上传一份报告,没有推理链 |
| MAT | 桌面端堆转储分析 | 功能最强大但也是最重的------Eclipse RCP 桌面应用,无法嵌入 Web,没有 REST API |
| async-profiler | CPU/分配采样 | 数据采集层做得极其出色,但采集后需要人工分析,没有分析层和推理层 |
| jvisualvm | 桌面端监控 + 诊断 | JDK 9 后不再随 JDK 发布,基本停止维护 |
这些工具共同的问题是:它们都是"单次、单维度、无状态"的------上传一份文件,得到一个报告,然后结束。它们不会记录你上一次的分析过程,不能跨 GC 日志和堆转储做关联推理,更不能主动提醒你"上一步看完了,下一步应该看什么"。
截止目前,市面还没有一个产品能把 AI Agent 和 Java 诊断工具有机结合起来,让 Agent 代替人完成跨工具的上下文切换和推理。这正是我们开发这个项目的动机------让 Java 性能分析从"手工作坊"走向"人机协作"。
1.1.2 知识门槛
Java 性能诊断工具的门槛不仅仅是"会不会用",而是"这些工具有极其复杂的版本和配置依赖"。
以 GC 日志解析为例,你要面对的格式变体包括:
| 维度 | 变体 |
|---|---|
| JDK 版本 | JDK 8(PrintGCDetails)vs JDK 9+(Unified Logging) |
| 收集器 | G1 / Parallel / ZGC / Shenandoah / Epsilon / CMS |
| G1 子类型 | Young / Mixed / Full / Concurrent / InitialMark / Remark / Cleanup |
| 日志级别 | info / debug / trace |
线程转储的格式分裂更严重:
| JDK 版本 | 格式 | 关键技术挑战 |
|---|---|---|
| 8-20 | 经典文本 | 正则提取,各版本基本兼容 |
| 21+ | 文本 + 虚拟线程 | Carrier/Mounted 段,java.base/ 模块前缀 |
| 26+ | JSON | threadContainers 嵌套结构,parkBlocker / monitorsOwned |
JDK 26 完全改用了 JSON 格式,这意味着之前所有基于正则的 jstack 解析器全部失效。
堆转储分析的门槛则来自工具本身。MAT 是一个 Eclipse RCP 桌面应用,它的学习曲线包括:
- 支配树(Dominator Tree):理解保留集(retained set)和浅堆(shallow heap)的区别
- OQL:类似 SQL 但语法和语义都有差异
- Leak Suspects Report:自动生成但误报率不低,需要人工辨别
- GC Root 路径:需要理解 WeakRef、SoftRef、PhantomRef 的区别
这些知识即使对一个有经验的 Java 开发者,也不是可以快速掌握的。
结果就是:团队中能做深度性能诊断的永远是那少数一两个人。他们成了"瓶颈",其他人遇到问题只能等着。
1.1.3 经验不可复用
每一次生产问题的排查,实际上产生了大量的隐形知识:
- 当时用了什么命令?带了什么参数?
- 为什么先看这个再看那个?推理链路是怎样的?
- 观察到了什么模式?和上一次有什么异同?
但这些知识几乎全部散落在聊天记录、Wiki 页面、或者个人的笔记本里。下一次遇到同类问题,往往要从头走一遍同样的流程。
更讽刺的是,最常发生的情况是"我知道这个问题我遇到过",但想不起来上次是怎么解决的。于是又回到了"先 jstat 看一下"的起点。
1.2 AI Agent 带来的范式转变
1.2.1 从"人找工具"到"工具找人"
传统的诊断流程是工具为中心的:
人知道有什么工具 → 知道参数怎么配 → 知道怎么看结果 → 知道下一步做什么
这个模型要求人同时具备"广度知识"(所有工具的用法)和"深度判断"(从数据中得出结论)。
AI Agent 的介入改变了这个模型。现在诊断流程变成:
人描述问题 → Agent 自动选择工具 → 分析结果 → Agent 推理 → 呈现结论 + 建议下一步
这里的关键转变是:Agent 承担了"操作层"的工作------它知道什么场景用什么工具、参数怎么传、结果怎么解析。人专注在"决策层"------判断 Agent 的结论是否合理,决定下一步怎么做。
一个实际的对比:
markdown
用户: "帮我看看这个 GC 日志,是不是有内存泄漏?"
传统流程:
1. 打开文件,人工识别 JDK 8 vs 9+ 格式
2. 找 Full GC 事件,计算频率
3. 推算每次 Full GC 前后的堆占用趋势
4. 查 p95/p99 停顿时间
5. 综合判断: "有泄漏风险"
6. 写排查报告
Agent 流程:
1. 自动格式检测 → JDK 17 G1
2. 237 个事件中 Full GC 发生了 5 次
3. 堆水位规则触发: 连续回收后占用率从 54% → 92%
4. 泄漏风险评估: high
5. 输出: "Full GC 后堆持续上升,建议做堆转储分析"
Agent 不会取代工程师的判断力,但它消除了"从数据到判断"之间的繁琐操作。
1.2.2 自然语言即接口
Agent 模式最有价值的变化是:用户可以用自然语言描述需求,而不是用命令行参数。
过去工程师要回忆的是:
jstat -gcutil <pid> 1000和jstat -gccause <pid>的区别jcmd <pid> GC.heap_info的参数顺序-XX:+PrintGCDetails在 JDK 11 的对应项
现在工程师只需要说:
- "看看堆有没有问题"
- "最慢的几次 GC 是什么时候?"
- "分析一下这个线程在做什么"
语言的抽象层次提升了。过去人是"翻译"------把"我想知道堆有没有泄漏"翻译成"先 jmap -dump,然后用 MAT 打开,再看支配树"。现在 Agent 承担了这个翻译工作。
1.2.3 多工具编排与自动推理
单一工具的分析往往不足以回答一个完整的问题。GC 日志看出泄漏风险后,需要堆转储确认;线程转储发现大量 BLOCKED 后,需要查看具体锁的持有者;OOM 分析需要同时看泄漏嫌疑人、线程中的 SQL 和支配树。
Agent 处理这种多步骤推理的自然方式是 ReAct 循环:
yaml
用户: "系统最近频繁 OOM,帮我分析一下"
Round 1:
Thought: 先看看 GC 日志,了解堆的使用情况
Action: analyze_gc_log(file_id)
Observation: JDK 17 G1, 5 次 Full GC, 堆水位从 54% 升到 92%
Round 2:
Thought: 有泄漏风险,需要做堆转储分析
Action: mat_overview(rid)
Observation: 已用堆 6.2GB/8GB, 泄漏嫌疑人 3 个
Round 3:
Thought: 最大的泄漏嫌疑人在哪里?
Action: mat_leak_suspects(rid)
Observation: Suspect #1: 2.8GB retained, 持有链为 ClassLoader → static HashMap
Round 4:
Thought: HashMap 里存了什么?用 OQL 查一下
Action: mat_oql(rid, "SELECT * FROM java.util.HashMap$Node")
Observation: 大部分是缓存数据,TTL 未设置
Final: "OOM 的根本原因是静态缓存无限制增长。建议:设置缓存上限或引入 TTL 淘汰机制。"
这 4 轮对话背后是 4 次不同的工具调用,覆盖了 GC 分析、堆概览、泄漏检测、OQL 查询 4 个领域。如果由人工操作,需要在终端、MAT 桌面端之间反复切换 4 次。由 Agent 操作,用户只需要发起一次请求,然后观察推理链的输出。如果某个结论不放心,还可以进一步追问。
1.2.4 经验可保存、可复用
每次 Agent 诊断的完整工具调用链都可以被记录和回放。这意味着:
- 新人可以学习"正确"的排查路径:Agent 的每一步操作都是可见的
- 同类问题可以一键复现:下次遇到同样模式,Agent 可以给出相同的推理链
- 长期记忆持续积累:用户的偏好("我们的服务都是 JDK 17 G1"、"我们使用 HikariCP")会被自动记住,在后续对话中隐含使用
1.3 技术全景
这个项目是一个基于 LangGraph 构建的 Java 性能分析 AI Agent。它的核心是:用有向图编排 LLM 的思考和诊断工具的执行,让 Agent 具备多轮推理和跨工具协作的能力。
scss
用户输入 (FastAPI 接收)
│
▼
LangGraph StateGraph 编排
│
├─ LLM 思考节点 (function-calling / 文本 ReAct fallback)
├─ 诊断工具调度
│ ├─ GC 日志分析 (格式检测 + 统计 + AI 诊断规则)
│ ├─ 线程转储分析 (多版本兼容 + 虚拟线程)
│ ├─ 堆转储分析 (MAT 查询服务 + 异步 OOM 诊断)
│ └─ JVM 参数验证 (参考文件比对)
└─ 强制总结节点 (超限场景)
│
▼
SSE 流式输出 (推理过程可见)
│
▼
前端可视化 (对话 + 数据图表双面板)
1.3.1 LangGraph 有向图编排
与传统的"if-else 判断 LLM 输出 → 调用工具 → 返回结果"的线性流程不同,这个系统把一个诊断会话建模为有向图:
sql
START → LLM 思考节点
→ 有工具调用 → 工具执行 → 结果回填 → 继续 LLM 思考
→ 无工具调用 → 输出最终回答
→ 超迭代上限 → 强制总结
这个图结构让 Agent 具备了"多轮推理"的能力------它不是一次性给出答案,而是边看边想。每次工具调用的结果会影响 LLM 的下一步决策。
图中有两个 LLM 节点:一个使用原生 tool_calls(function-calling),一个使用纯文本 ReAct(Action: / Action Input: 格式)。后者作为 fallback,用于不支持 tools 参数的 LLM 提供方。系统在运行时自动检测降级。
1.3.2 诊断工具系统
要让 Agent 具备 Java 性能分析的能力,需要设计一套覆盖完整排查路径的工具系统:
数据采集与解析:Agent 能接收用户上传的诊断文件(GC 日志、线程转储、堆转储等),自动识别文件类型和对应的 JDK 版本。用户不需要手动指定"这是 JDK 17 的 G1 GC 日志"------Agent 自己就能检测出来。
统计分析:文件解析完成后,需要对数据进行统计加工。GC 日志的暂停时间百分位、线程转储的状态分布、堆转储的类直方图------这些统计是诊断规则的基础输入。
诊断规则引擎:统计本身没有意义,有意义的是从统计中发现异常模式。规则引擎编码了资深工程师的排查经验:堆水位持续上升是泄漏信号、大量线程处于 BLOCKED 是锁竞争、G1 不应该发生 Full GC......每条规则检查一个已知的异常模式,输出分级结论和推荐建议。
事件级钻取:整体诊断给出方向后,Agent 需要能向下追问细节------"显示 Full GC 中最慢的几次"、"看看这个线程的完整栈帧"、"查一下 OOM 时哪个 SQL 还在跑"。这种从宏观到微观的钻取能力,是 Agent 比静态报告更灵活的原因。
跨工具关联:GC 日志的结果可以导向堆转储分析,线程转储的结果可以关联到 JVM 参数建议。工具的边界对 Agent 来说是透明的------它只需要知道"当前的信息还不够,需要调另一个工具"。
结果持久化与回顾:每次分析的结论、工具调用链、关键数据都需要持久化。这样 Agent 可以跨会话记住用户偏好,也可以让用户随时回溯之前的分析过程。
1.3.3 三层堆转储分析架构
堆转储分析是唯一需要外部 Java 进程支持的能力------MAT 是 Eclipse RCP 桌面应用,无法直接嵌入 Python 后端。系统采用三层架构解决这个问题:
scss
浏览器 ──HTTP──→ FastAPI Web ──httpx──→ MAT Query Service (Java)
│
Worker (独立进程)
│
subprocess MAT (Eclipse Equinox)
Web 层负责上传、报告管理和查询反代。Worker 进程从任务表中领取解析任务,启动 MAT 子进程,将解析进度实时写回数据库。MAT Query Service 是无状态的 Java 进程,提供 RESTful 分析 API(概览、直方图、支配树、OQL、泄漏检测等)。耗时较长的分析(如泄漏嫌疑人检测、OOM 综合诊断)采用异步提交 + 轮询的方式,避免长时间阻塞 Agent 循环。
1.3.4 SSE 流式输出
工具调用可能耗时数秒,用户不能干等。系统通过 SSE 流式输出 Agent 的推理过程:
ini
token(phase="reason") → "我正在分析 GC 日志..."
tool_use → analyze_gc_log
token(phase="reason") → "堆水位持续上升,我需要看看最慢的几个事件..."
tool_use → query_gc_events
tool_result → "3 次 >500ms 的 Full GC 事件"
token → 最终诊断结论
done
这个流不仅让用户感知进度,更重要的是让 Agent 的"思考过程"透明可见------用户可以看到 Agent 为什么调用某个工具、看到了什么结果、基于什么推理出结论。
1.3.5 前端可视化
前端核心要解决的问题是:Agent 的文本输出和诊断数据的图表,应该以什么方式组合在一起。
纯文本的诊断结论信息密度高,但不利于模式识别------一堆数字远不如一张趋势图直观。纯可视化界面(如 GCViewer)又缺少 Agent 交互的灵活性和推理链的表达。
最终的方案是双面板布局:

左侧是 Agent 的对话面板,展示完整的推理链------每一步的 Thought、工具调用、结果、最终结论。用户可以看到 Agent"想了什么、做了什么、看到了什么"。这不仅是聊天记录,而是可追溯的诊断日志。
右侧是数据可视化面板,根据当前诊断的主题动态切换内容:
- GC 分析时,展示堆使用趋势图、暂停时间分布柱状图、类别饼图
- 线程转储分析时,展示线程状态分布、锁热点表、火焰图
- 堆转储分析时,展示类直方图、支配树、泄漏嫌疑人报告
两个面板不是割裂的。用户在右侧看到某个图表上的异常(比如堆水位在某个时间点骤升),可以在左侧直接提问"这个时间点发生了什么",Agent 会理解上下文并进行钻取。反之,Agent 在推理过程中也可以主动将特定数据渲染为图表嵌入右侧面板。
这种设计兼顾了两种需求:Agent 的推理需要自然语言的灵活性,数据的呈现需要可视化的直观性。
1.4 小结
Java 性能分析领域长期存在三个系统性的断层:工具割裂、知识门槛、经验不可复用。AI Agent 的引入改变了这个局面------它把工程师从繁琐的操作层解放出来,让诊断的本质回归到分析和判断本身。
LangGraph 有向图编排提供了灵活的多轮推理能力,诊断工具系统覆盖了 GC、线程、堆、JVM 参数四个核心领域,SSE 流式输出和双面板前端设计保证了交互的实时性和透明性。