用 TraeCode 排查 Spring Boot 内存泄漏,从 GC 日志到 Heap Dump 全记录

组里有个跑了大半年的 Spring Boot 订单服务,最近两周开始出现一个诡异的问题:每天早上 10 点左右,JVM 堆内存就飙到 90% 以上,GC 频率从几分钟一次变成几十秒一次,偶尔直接 OOM 重启。之前同事用 jstat 和 jmap 看过几次,没找到泄漏点,事情就一直拖着。

做了四五年 Java 后端,内存泄漏这种问题不是没见过,但每次都挺头疼的——Heap Dump 几十个 G,MAT 打开慢得要死,分析起来全靠经验猜。这次正好在试用 TraeCode,就想着能不能让 Agent 帮忙一起排查,看看 AI 在性能调优这块到底能帮到什么程度。

第一步:把报错日志和 GC 日志丢给 Agent

我没有一上来就让 TraeCode 帮我"修 bug",而是先把两周的 GC 日志(-Xloggc 输出的)和几次 OOM 前后的应用日志整理成一个文本文件,直接丢进对话里,跟 Agent 说:

“这是一个 Spring Boot 2.7 + JDK 11 的订单服务,最近两周每天早上 10 点左右堆内存飙到 90%+,GC 频率异常。这是 GC 日志和应用日志的关键片段,帮我分析一下内存增长的模式。”

Agent 看完日志后,直接指出几个关键信息:

  • Full GC 频率从正常的 4 小时一次,恶化到 20 分钟一次
  • 每次 Full GC 后老年代回收率从 60% 逐步降到 15%,说明有对象持续堆积且无法回收
  • 内存增长曲线不是突增而是缓慢爬升,更符合慢性泄漏的特征

这一步省了我至少半小时手动翻日志的时间。

第二步:让 Agent 帮忙分析 Heap Dump

我用 jmap 导了一份 Heap Dump(大概 3G),用 MAT 的 Top Consumers 导出了一份 CSV(包含占用内存最大的 50 个类)。把 CSV 丢给 TraeCode:

“这是 MAT 导出的 Top Consumers 数据,帮我看看哪些类的实例数量和内存占用不正常,重点排查跟订单业务相关的类。”

Agent 很快锁定了两个可疑点:

  1. com.xx.order.model.OrderContext 实例数 47 万+,占用堆内存 1.2G,但这个类按理应该是请求级别的短生命周期对象
  2. java.util.concurrent.ConcurrentHashMap$Node 实例数 120 万+,其中大部分 key 是 UUID 格式字符串

Agent 推测:某个地方把本该回收的 OrderContext 对象塞进了一个全局的 Map 里,而且 key 是 UUID,很可能是某种"会话缓存"或者"分布式事务上下文"之类的东西。

第三步:定位代码 + 修复

有了方向,我直接在 TraeCode 里打开项目,用 SOLO 模式让它搜代码:

“在项目中搜索所有引用了 OrderContext 的地方,特别是把它 put 进 Map、List、Cache 的代码。重点关注 @Component@Service 这种单例 Bean 里的成员变量。”

Agent 扫了一遍代码,10 秒钟就找到了问题根源——一个 OrderStateMachineService@Service 单例),里面有一个 ConcurrentHashMap<String, OrderContext> pendingOrders,用来存储"待处理"的订单上下文。问题是这个 Map 只有 put 没有 remove,订单处理完后没人清理。

更离谱的是,这个 Map 还没有设置过期策略,也没有定时清理任务。服务跑了大半年,积累的订单上下文越来越多,直到内存撑不住。

修复方案也很简单:

  1. 订单处理完成后立即 remove
  2. 加一个 @Scheduled 定时清理超过 30 分钟的过期条目
  3. 把 ConcurrentHashMap 换成 Caffeine Cache,设置 maximumSize 和 expireAfterWrite

改完跑了一天,内存曲线稳得像一条直线,Full GC 恢复到 4 小时一次。

最后做成什么

  • 定位了一个隐藏大半年的内存泄漏,根因是单例 Bean 里的 ConcurrentHashMap 只进不出
  • 修复代码不到 20 行,加了清理逻辑 + 换成 Caffeine Cache
  • 整个过程从分析日志到定位代码到修复,大概花了 2 个小时。之前同事手动排查了两次都没搞定,每次花半天最后没结论

几个体会

  1. Agent 不会替你思考,但能帮你快速缩小范围。比如 47 万个 OrderContext 实例这种信息,人眼翻 MAT 报告很容易忽略,但 Agent 一眼就能看出"这个类的实例数量不正常"
  2. 日志和数据分析是 TraeCode 最擅长的场景之一。你不需要自己写脚本去 parse GC 日志,直接把原始数据丢给它就行
  3. 代码搜索这块 SOLO 模式很好用,比 IDE 的全局搜索更智能——它不是简单的关键词匹配,而是理解你的意图去找相关代码