前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一次线上内存泄露历险

一次线上内存泄露历险

作者头像
曲水流觞
发布2019-10-28 00:24:39
1.8K0
发布2019-10-28 00:24:39
举报
文章被收录于专栏:曲水流觞TechRill

故事

刚进公司那段时间,在敏捷项目制的执行下,需求有条不紊地进行着。某个周末,业务系统反馈群内,操作人员反馈系统不可用,我们急忙寻求运维的帮助,将系统重启并恢复使用。同时排查相关log,检查异常点,但是根据log并没有跟踪出结果。于是想到是否有OOM的dump文件生成,询问运维后,被告知并没有生成。咨询之前的应用负责人,以前也有类似系统不可用情况,但只是偶现。没有办法,根据应用日志查不出结果,只有下次复现时导出dump彻查了。又过去一段时间,故障反馈群里又是一样的问题,于是赶忙麻烦运维把dump生成,然后重启了应用,同时离线对dump进行了分析。

通过分析,在内存泄漏的可疑点内,PoolingHttpClientConnectionManager这个类映入眼帘,jvm居然包含了近15万个该类的实例,所占内存大小是1,918,318,216 bytes,换算成Mb就是1829M,如此多的实例没有被回收,势必会造成OOM,导致系统不可用。

于是查找源码,发现是操作阿里云oss的相关代码,IdleConnectionReaper类的变量有一个ArrayList,是由static修饰的,由static修饰想必大家都知道结果了: 这类强引用,虚拟机GC是无法进行回收的。

然而问题来了,为何ArrayList里的HttpClientConnectionManager对象一直在增加?

带着疑问我们查看了代码中OSS的调用入口,在调用入口处,发现一处可疑代码:

再看摘录的相关调用,发现了HttpClientConnectionManager对象一直在增加的根源

但是翻阅IdleConnectionReaper类的源码时,发现了removeConnectionManager方法,用来移除ArrayList内的HttpClientConnectionManager对象。

同时我们对比了阿里云oss官方给的demo和涉及类的类图, demo中是调用了shutdown方法,shutdown的实现就是调用IdleConnectionReaper的removeConnectionManager方法。

回过头来,在我们的代码里并没有搜索到shutdown相关的调用。也就是说,每次调用oss,都会往IdleConnectionReaper的connectionManagers变量里增加对象,而且整个生命周期内都没有对其进行对象移除,最终导致内存溢出。

解决方案要么在方法调用的最后进行shutdown操作;要么就避免对象一直创建,用连接池进行管理,提供性能和效率。

于是我们联系了基础服务组,报告了该问题。基础服务组给出了补丁,我们也配合进行了验证,并上线进行了修复和观察,这段内存泄漏的经历便告一段落。

疑问

有一个问题一直困扰着我们,随着时间的推移,有问题的那个静态变量ArrayList迟早会把内存撑爆掉,理论上该问题应该在线上一直存在,为何一直没暴露(或者说偶尔暴露)。我们带着疑问对这次经历进行总结和复盘:

知识点总结

说完故事,就要来总结枯燥的知识点了。大家都知道这次问题的罪魁祸首是内存泄漏。而什么是内存泄漏,导致内存泄漏的原因是什么,出现疑似内存泄漏后又该如何定位呢?

1. 内存泄漏的定义

内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。

2. 引起内存泄漏的几种情况

2.1 静态集合类引起内存泄漏

故事中static修饰的ArrayList是一个绝佳的例子,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们一直被引用着。

2.2当集合里面的对象属性被修改后,再调用remove()方法时不起作用

例如HashMap、HashSet,当集合内的对象属性参与了hash的计算,改变对象属性后,再去调用remove()方法,无法将集合内的对象移除。

2.3监听器

在释放对象的时候却没有去删除这些监听器,增加了内存泄漏的机会。

2.4各种连接

比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。

2.5单例模式

不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏。

3. 定位内存泄漏的相关工具

在本文故事里我们用到了下面这些工具来辅助我们定位内存泄漏:

3.1 Java自带的强大工具

jstat: 虚拟机统计信息监控工具--可实时查看目前虚拟机相关统计信息。

使用场景:利用jstat可以快速查看当前时刻jvm的gc情况,是否有full gc过于频繁一目了然。

jmap: 虚拟机内存映像工具—可生成即时虚拟机内存dump,供离线分析。

使用场景:在jvm启动参数里我们可以通过-XX:+HeapDumpOnOutOfMemoryError和-XX:+HeapDumpPath来设置发生OOM时导出堆到文件,或者我们可以通过jmap来手动生成堆转储文件。

jvisualvm:可视化工具,可实时分析内存占用、gc、线程等。

使用场景:需要实时分析虚拟机内存时使用,可直观看到堆使用情况

jconsole:和jvisualvm大同小异,都是可视化的工具。

3.2 Eclipse的M(emory)A(nalizer)T(ool)

Eclipse MAT是一个快速且功能丰富的Java Heap分析工具, 可以帮助我们寻找内存泄露, 减少内存消耗。MAT可以分析程序生成的Heap dumps文件, 它会快速计算出对象的Retained Size, 来展示是哪些对象没有被GC, 自动生成内存泄露疑点的报告。

使用场景:应用dump文件生成后,导入至MAT中,可快速生成内存泄漏的报告,以供分析。

这些工具的具体使用方法都可以在搜索引擎里检索到,这里就不深入展开了。

3.3 开源的实时监控系统

开源的实时监控系统有很多,这里我们谈到的是CAT。CAT是大众点评开源的监控项目,有一项heartbeat面板可以实时查看应用的gc情况,内存使用情况,这对下面的偶现问题排查起到了关键作用。

偶现问题原因排查

结合实际我们对线上偶现的问题进行剖析,并初步提出几点推测:

1. 最近oss封装版本有更新,引入了未调用shutdown方法的bug

通过抽样查阅oss几个历史的封装jar包,均未包含shutdown方法,说明问题一直存在,排除该原因。

2. 是否有人手动重启应用,短时间内避免了内存溢出

联系运维同学确认后,并未有人手动重启应用,排除此可能。

3. 应用敏捷迭代更新

可能有同学会有疑问,敏捷迭代怎么会导致问题偶现。我们目前的敏捷迭代周期是两周,如果应用的old区容量能够撑两周,那么迭代上线,就会让应用重启,从而使得jvm的世界从头开始,内存溢出就不会暴露;而随着系统的频繁使用和需求的饱和,应用不能保证每次都能在old区满之前来个上线让应用重启,于是内存溢出就出现了,当然这只是个推测,需要证据来支撑。

偶然的一次监控告警,发现了CAT上有个Heartbeat面板,展示各个应用的gc情况和堆内存使用情况,于是查看了历史old区使用情况,果然有一个时间点出现old区使用容量骤降,再匹配时间点,恰好是有应用上线。CAT的证据支持了我们的这一推断,解决了偶现问题的困扰。

问题过程回顾与建议

1. 系统使用人员报告故障

2. 联系运维查看gc回收情况(使用jstat命令)或查看CAT的heartbeat面板

3. 联系运维生成当时的heap dump文件(使用jmap命令)

4. 应用重启恢复使用(临时解决系统不可用)

5. 离线分析原因

5.1 离线分析dump

5.2 结合监控平台CAT查看应用不可用前后jvm内存情况和gc情况

5.3 分析出内存泄漏点

6. 本地开发环境尝试问题复现

7. 找出问题源并联系相关人员修复

8. 更新修复补丁并验证问题是否解决

当碰到疑似内存泄漏问题,可以参考以上过程回顾,如果设置了HeapDumpOnOutOfMemoryError却没有生成堆转储文件的,一定要联系运维手动生成堆转储再进行重启,否则就错失了分析dump的绝佳时机;至于在coding时如何避免内存泄漏,只需针对造成内存泄漏的几点原因稍加规避即可。

参考文献:《深入了解Java虚拟机》

部分定义引自:https://blog.csdn.net/wtt945482445/article/details/52483944

部分源码来自阿里云

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-07-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 曲水流觞TechRill 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档