记录一次内存泄漏的排查过程

问题描述

工作中发现,在对A服务的B接口压测之后,发现内存占用一直降不下来,怀疑有内存泄漏,需要进一步排查

排查思路

通过咨询chatgpt ,确定思路:

1,使用内存分析工具(如Eclipse Memory Analyzer、VisualVM、MAT等)来分析堆转储快照(heap dump),查找潜在的内存泄漏问题

2,检查GC日志中的频率、持续时间和回收的内存量等信息,可以确定是否存在内存泄漏或频繁的全局垃圾回收事件

3,代码审查:检查Java服务的代码,特别关注内存管理方面的实现。查找是否有未关闭的资源(如数据库连接、文件流等)、缓存未正确释放、对象生命周期管理不当等问题。

内存分析

使用开源的 Eclipse Memory Analyzer 工具进行分析

1. 导出 UAT 环境A服务的内存快照

(经过确认,UAT和生产有相同的问题,尽可能不动生产环境)

2. 将转储文件导入到分析工具

3. 查看结果

3.1 先看分析工具提供的内存泄漏检测报告

3.2 理解问题

如图所示,报告说有 7295 个org.apache.http.impl.conn.PoolingHttpClientConnectionManager 对象占据了 186245928 字节,约等于 52.46% 的内存空间,并且被一个 java.util.concurrent.ConcurrentHashMap$Node\[\] 对象引用,简单理解,就是一个ConcurrentHashMap里面有7295个对象占据了大量内存,疑似内存泄漏

3.3 获取更多线索

一个 PoolingHttpClientConnectionManager 还不够,为了获得更多线索,可以查看对象之间的引用关系,获取更多信息。

进入对象直方图,找到PoolingHttpClientConnectionManager的统计信息

通过查询 GC Roots 功能,查询引用这些对象的其他对象

分析可知:

有一个 software.amazon.awssdk.http.apache.internal.conn.IdleConnectionReaper$ReaperTask 对象引用了一个 java.util.concurrent.ConcurrentHashMap 对象,然后这个map里面有大量的 PoolingHttpClientConnectionManager 对象

4. 定位并解决问题

4.1 先找到对应的类

4.2 合理设置断点

发现 connectionManagers 只有两个地方在操作这个map,一个put,一个remove

put 和 remove 操作打上断点

4.3 调试

debug调用压测的接口,发现卡在put方法的断点了,并且根据方法调用栈显示,SdkDefaultClientBuilder 的 build 方法是源头,这个方法是用来创建 S3Client 的

继续执行代码,发现没卡在 remove 方法的断点上

到这里问题已经比较明显了:

每次调用该接口时,都会创建一次 S3Client,每次创建都会把一个新的 PoolingHttpClientConnectionManager 对象放到一个 map 里面,并且不会 remove,同时,我们继续研究代码发现

ReaperTask 是一个循环任务,该对象会一直引用这个connectionManagers,connectionManagers 又引用了大量的 PoolingHttpClientConnectionManager对象, 这会导致 connectionManagers 以及内部的 PoolingHttpClientConnectionManager对象 不会被 GC 回收(GC可达性分析:可达的对象不回收,不可达对象作为"垃圾"被回收)

4.4 修复问题

找到出问题的代码块时,发现编辑器已经给出了提示,S3client 没有被关闭

通过验证发现,调用S3client 的关闭方法确实会移除 connectionManagers 里面的PoolingHttpClientConnectionManager对象

也就意味着每次调用接口都会创建一个S3client,并且put一个PoolingHttpClientConnectionManager对象到一个map中,如果不关闭S3client,这个 PoolingHttpClientConnectionManager 就会造成内存泄漏

将原来的代码改成try-with-resource 语句就能解决问题

4.5 服务端验证

将最新代码发布到UAT环境,执行压测脚本,然后导出内存快照

可以观察到 PoolingHttpClientConnectionManager 对象的数量已经少了很多

同时这些对象中大部分都是被 Finalizer 引用的,这意味着这些对象很快会被回收掉

至此,说明该问题已经解决

总结

写代码需要规范,不熟悉的api需要参考文档,参考最佳实践。同时,多留意代码检查插件和编辑器的提示信息。

相关推荐
ZealSinger6 分钟前
LangChain4j用AiServices拼Tool
java·rag·ai智能体·langchain4j
vx_Biye_Design15 分钟前
springboot高校选课系统82776-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·spring·课程设计·express
Sweet锦1 小时前
不调 Python,不装向量库:我用纯 Java 写了一套以图搜图引擎
java·人工智能·开源·图搜索
打工仔折腾 AI2 小时前
LLaMA 1 到 LLaMA 3 架构演进拆解:从 RoPE、GQA 到词表扩张
人工智能·后端·python·深度学习·langchain·llama
如意猴2 小时前
【C++】007 C/C++ 内存管理机制、malloc与new的区别及模板初阶
java·c++·算法
可乐鸡翅yeah_2 小时前
HLS 分片过期清理,直播旧 TS 分片磁盘爆满问题处理
java·后端·spring·m3u8·m3u8在线·音视频在线播放
张彦峰ZYF3 小时前
加了锁不等于扛得住争用:synchronized、显式锁与 CAS 的语义分层、争用代价账本与选型判据
后端·同步设施·内置锁的四种状态与单向升级·可重入的由来与四条硬边界·显式锁的状态字段与等待队列·读写锁与邮戳锁·一次同步的开销账本
专业程序开发源3 小时前
SSM校园拍摄交流服务平台36936-计算机课程设计、毕业设计
java·spring boot·后端·python·elasticsearch·php·课程设计
Gopher_HBo3 小时前
zap采样器与性能优化内幕
后端