Java内存泄漏问题分析
CLOSE_WAIT成因:程序未正确响应TCP连接的关闭流程,需检查或HTTP客户端释放逻辑。内存泄漏排查:通过堆转储(Heap Dump)分析最大对象占用,结合业务日志定位增长源头。缓存设计:确保缓存键的唯一性和有效性,避免“伪缓存”导致资源泄漏。// 修复后:使用统一缓存键并显式关闭旧连接// 释放旧连接。
目录
分析阶段一
分析阶段二
内存泄漏也是一个老八股文了,下面来看看实际项目中内存泄漏的场景分析
时间回到9月某一天
分析阶段一
现象:在当时各种请求在那段时间响应很慢,特别是 kafka异步消费线程
不足点:当时主业务基本不可用,有点急,未及时dump(当时大家没往GC那方面想,单纯以为流量大消费不过来)
第二天系统拉了日志也还未分析出来,只是临时增加了kafka消费线程的数量,单机从2扩到10,两台机器共计20消费线程,服务都重启了一遍
又过去了几天,来到了节假日流量高峰期
分析阶段二
leader在群里发了几个阿里云告警信息,内存告警(这个时候已经距离上次程序重启好几天了)
dba也在群里发了异常信息,有发截图(截图中大量连接状态为 CLOSE_WAIT 以及 重复ip:121.51.50.140和121.51.58.151):句柄数一直在涨
注:当时我回复dba的话:句柄增加,在我印象里,业务代码里应该没什么本地IO操作,网络IO的话主要是我们服务之间会频繁的进行远程调用(-_-害 把订单模块给漏了,这里会去调阿里/腾讯进行支付)
当时在群里询问小伙伴有没有可以 根据句柄反推请求的方法,却把截图中的两个关键信息给漏了,还是经验不足 -_-
负责订单的同事上线了,发现两个突破点:
1、大量的连接状态是 CLOSE_WAIT
如果一直保持在CLOSE_WAIT状态,那么只有一种情况,就是在对方关闭连接之后服务器程 序自己没有进一步发出ack信号。换句话说,就是在对方连接关闭之后,程序里没有检测到,或者程序压根就忘记了这个时候需要关闭连接,于是这个资源就一直 被程序占着。个人觉得这种情况,通过服务器内核参数也没办法解决,服务器对于程序抢占的资源没有主动回收的权利,除非终止程序运行。
所以如果将大量CLOSE_WAIT的解决办法总结为一句话那就是:查代码,因为问题出在服务器程序里头啊
2、重复ip:121.51.50.140和121.51.58.151 (此为腾讯云服务器)
121.51.58.151:https (CLOSE_WAIT)
121.51.58.151:https (CLOSE_WAIT)
121.51.50.140:https (CLOSE_WAIT)
AI写代码
为什么有大量的连接处在CLOSE_WAIT状态? (qq.com)
TCP通信过程中time_wait和close_wait产生过多的原因和解决方法_time_wait连接过多的原因-CSDN博客
同时根据这两点定位到代码:
1、获取支付渠道配置(微信支付 微信支付分…)
2、根据获取到的配置判断httpClientMap中是否存在(会对比版本号),不存在则创建一个WechatPayHttpClient(或支付宝 或其他渠道)然后写入httpClientMap
乍一眼看流程没啥问题,其实在第一步每次都会获取一个“新”的渠道配置(版本号不一致),因渠道配置缓存的set get 使用的key不同,等同于没缓存(这个缓存为本地hashmap,value为连接对象)
没缓存所导致的问题就是 每次支付都会重新build 一个新的连接去调用支付渠道进行支付,然后每次都会将这个连接放入map中,此时内存就会随着支付的请求数量而持续增高,并且连接对象也不会被释放掉。在修复之前也通过dump文件看到最大的对象即是这个存储连接对象的map,最终展现的情况就是一直Full GC,所有业务请求在那段时间响应非常慢,连一个最基本的主键查询都很慢-_-
写到最后:此文是一位优秀且帅气的同事所排查分析,作为小菜鸡的我只能在此案例中默默学习学习๐·°(৹˃̵﹏˂̵৹)°·๐
————————————————
版权声明:本文为CSDN博主「北i」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_44700876/article/details/135332592### 内存泄漏场景分析
现象描述
系统在9月某段时间出现请求响应缓慢,Kafka异步消费线程不足导致主业务基本不可用。初期未考虑GC问题,仅通过临时增加Kafka消费线程数量缓解问题(单机从2扩至10)。后续在节假日流量高峰期间,阿里云告警显示内存异常,同时DBA发现大量TCP连接处于CLOSE_WAIT状态,且句柄数持续上涨。
关键发现
CLOSE_WAIT状态连接过多
该状态表明远端已关闭连接,但本地程序未主动发送ACK或关闭连接。根本原因是程序未正确释放资源,需通过代码修复而非调整内核参数。- 重复IP连接
发现大量指向腾讯云服务器(121.51.50.140和121.51.58.151)的CLOSE_WAIT连接,结合业务逻辑定位到支付模块的HTTP客户端管理问题。
代码层面问题
- 支付渠道配置缓存失效
每次支付请求会获取“新”的渠道配置(版本号不一致),导致本地HashMap缓存失效。 - 连接对象泄漏
每次支付请求重建WechatPayHttpClient并存入httpClientMap,但旧连接未释放。随着支付请求量增长,Map持续膨胀,引发内存泄漏和频繁Full GC。
解决方案
- 修复缓存逻辑
确保支付渠道配置的缓存键一致,避免重复创建连接对象。 - 显式释放资源
在HTTP客户端使用完毕后,主动调用关闭方法释放连接。 - 引入连接池
改用连接池管理支付渠道的HTTP客户端,限制最大连接数并自动回收闲置连接。
后续改进
- 监控
CLOSE_WAIT连接数,设置阈值告警。 - 定期检查本地缓存的内存占用,避免无限制增长。
- 关键服务增加内存Dump自动化机制,便于快速定位问题。
技术要点总结
CLOSE_WAIT成因:程序未正确响应TCP连接的关闭流程,需检查socket.close()或HTTP客户端释放逻辑。- 内存泄漏排查:通过堆转储(Heap Dump)分析最大对象占用,结合业务日志定位增长源头。
- 缓存设计:确保缓存键的唯一性和有效性,避免“伪缓存”导致资源泄漏。
代码修复示例(伪代码):
// 修复后:使用统一缓存键并显式关闭旧连接
String cacheKey = "paymentConfig_" + channelType;
PaymentConfig config = configCache.get(cacheKey);
if (config == null || config.versionChanged()) {
if (httpClientMap.containsKey(cacheKey)) {
httpClientMap.get(cacheKey).close(); // 释放旧连接
}
config = fetchNewConfig(channelType);
httpClientMap.put(cacheKey, createNewClient(config));
}
更多推荐



所有评论(0)