MaWenge's Blog

一次线上内存泄漏排查

问题描述

2017春节前夕,车辆服务每隔三四天会出现宕机,周期也比较稳定,基本就是维持在三四天左右。宕机的原因是堆内存溢出。
初步判断造成内存溢出的原因是发生了内存泄漏,大量堆内的无用对象无法被回收,导致新创建的对象无法申请到足够的空间。

问题排查

由于是过年期间,技术部所有人都放假回家,服务负责人在家中进行问题排查,也没有发现什么头绪。由于马上春节,用户量会逐渐减少到0,业务量也不是很大。所以就暂时的应对方案是每隔两天手动重启一次服务,这种临时性的解决方案帮助我们度过了假期期间的业务。

年后回来,正式开始排查问题。2018/02/26上午,服务刚刚被重启,在阿里云后台监测ecs发现运行正常。8g的内存监控后台显示使用了2g,看似正常,并且所有请求处理也正常。连到服务后台进行查看。

查看虚拟机启动参数

可以看出最大堆内存为 2051014656/(1024 * 1024) = 1956M。
查看进程内存cpu占用情况

可以看到20814进程总共占用内存1.414g。
现在看起来都一切正常

再看一下gc的情况,每隔10秒采一次样

可以看出,到目前为止,每次新生代回收一次需要大概22ms,fullgc总共发生了11次,查询进程启动时间为31.5小时

fullgc相当于每2.86小时发生一次,每次fullgc使用的时间为5350/11=486ms,还算正常。

到目前为止没有发现异常,主要是服务启动时间也不长,但是如果等到服务不行的时候再次查看数据,这无疑拉长了问题解决周期,并且春节过后业务量逐渐恢复正常,问题出现的周期很有可能缩短。

所以,需要查看当前堆中内存分布情况。
整体思路有两个:
1 使用jdk自带的visualvm工具分析,最终放弃,原因主要有:
1)服务器不好直接运行visualvm这种可视化工具,visualvm可以支持连接远程服务器,但是貌似不好用,
2)visualvm对堆内对象分析功能不够强大,有些类型信息不能完整显示,而且没法显示引用关系。
这些都不能帮助我有效分析当前内存快照的情况。

2 还有就是使用jmap + mat组合。把进程当前内存快照保存成文件然后传到本地使用mat工具进行分析

这里一定要在登录用户为创建进程的用户的时候执行相关命令,否则会出现process not found的情况。
然后把生成的文件传到本地,使用mat分析。mat就是Memory Analyzer (MAT),它是eclipse的一个插件。用来分析二进制堆文件。

由上图可以知道,进程启动时间是 2018/02/26 08:58:29

2018/02/26 16:44:00我做了一次内存快照如下图


总内存138.6MB,其中54.6MB疑似内存泄漏,泄漏对象类型是com.taobao.txc.common.config.ConsoleConfig。

2018/02/27 09:48:00,再次做内存快照


此时可以发现,意思泄漏内存增长到175MB,泄漏对象类型是com.taobao.txc.common.config.ConsoleConfig。

2018/02/28 10:02:00,再次做内存快照



此时疑似泄漏内存增加到两处,一处是349.3MB,泄漏对象类型是com.taobao.txc.common.config.ConsoleConfig,
还有一处是51.6MB,泄漏对象类型是java.lang.ref.Finalizer。

问题分析

从上面三次做的内存快照可以看出,主要泄漏的对象就是com.taobao.txc.common.config.ConsoleConfig,可以看出是在ConcurentHashMap大量保存了该对象,查看该对象引用关系如下

这个类是我们项目中引用分布式事务组件当中的类,年前有一次升级,现在想起来就是升级之后才出现了这个问题。我们马上联系了分布式事务gts的同学,把这个问题反馈给了他们,他们发现后也很重视,马上就开始排查,到了下午就已经出了一个临时的紧急修复jar包。我们也马上更换并且重新部署所有相关服务。

结论

本次的泄漏问题还算比较温和,没有立刻使服务挂掉,基本上是三四天逐渐加剧,最终导致内存占满,服务奔溃。这就给了我们比较充足的时间来排查分析问题。从这次也验证了jmap + mat分析线上服务的能力非常强大。