问题现象:
运维通过内存监控发现,应用内存在每次跑批时会在短时间内(5分钟内)飙升10%左右(8G内存),且内存不会下降。
分析过程:
- 通过分析对应时间点的日志,分析可能是某个业务跑批导致,但查看代码并未能发现可能存在泄漏的地方;
- 通过代码扫描工具找到可能存在文件流未关闭的代码,修复后上线验证问题仍然存在,并无太大效果;
- 在内存飙升后,向运维申请生成JVM dump文件(起初运维不愿提供,来回折腾了一段时间);
- 使用MAT工具,结合代码分析dump文件:
4.1 应用进程使用了5-6G内存,而dump文件只有1G左右、通过MAT查看堆内存只有200MB,怀疑是堆外内存泄漏;
4.2 通过dump文件分析,发现MyBatis部分SQL缓存对象达到几十MB,查询SQL有优化空间,但不会造成很大内存泄漏; - 往堆外内存泄漏方向排查
5.1 通过MAT工具分析发现存在大量Finalizer对象、ByteBuffer对象;
5.2 Finalizer对象持有大量OpenSsl对象(27万个),该OpenSsl为Apache加解密包里的类;
5.3 定位到解密代码有用到ByteBuffer和CryptoCipher类;
5.4 发现解密代码while循环,会循环创建CryptoCipher和ByteBuffer对象,每次跑批解密的文本数据量达到27万条,问题基本确认; - 优化
定位到问题后,接下来就好办了。优化代码,CryptoCipher对象和ByteBuffer对象复用。上线后观察多日,跑批后再无内存飙升现象,搞定收工!
总结
本次内存泄漏,从问题出现到定位到问题原因拖了较长时间。主要有如下几个原因:
- 运维对dump命令不放心,申请dump文件僵持了一段时间;
- 生产服务器无法查看java堆内存情况,不利于确认堆外还是堆内泄漏的排查方向;
- MAT工具使用不够熟练;
- 堆外内存泄漏排查经验不足,未能敏锐的识别到可能造成堆外内存泄漏的相关类;
网友评论