记录一次内存排查过程
背景说明
收到运维同学的通知,通过查看服务POD内存情况,确实存在已很小的内存上升的情况,docker容器内只部署了一个服务,所以初步怀疑服务存在内存泄漏的情况。
压测环境对某查询类接口进行压测时,发现内存上升明显,故针对此接口进行针对压测分析,压测结果:
开始查找哪里有问题,一步步定位
分析代码问题
首先查看代码逻辑是否有存在对象释放不了的可能,是否查找了太多的对象,一系列操作,首先把最基本的问题排除掉再开始定位。
排查结果:接口代码确实没有问题,这里出现问题的概率不太大
JProfiler工具
代码没有问题的话,那么就只能通过查看内存内的对象去分析了,是否存在某不知道的对象被申请后没有释放,于是在Idea里安装了Jprofiler插件,并且安装了JProfiler软件,进行压测分析实时内存情况
两次短暂的并发请求后,内存出现了小幅度的上升,确实存在内存问题,接着查
因为没有长时间的进行压测,而且存在问题的对象很小,所以从实时内存的对象信息中并未得到有效的信息,因为基本对象被引用的比较多,所以不方便定位到,如果在这里发现大量增加的可疑对象,那就是这个对象有问题,这里因为对象比较少所以没有发现异常。
针对异常的对象,可以通过右键在堆遍历器中显示进行进一步的分析
这里可以进行进一步的分析他的引用,分配,最大对象,目前没有研究透传。
因为没有找到有问题的对象,所以找到压测的同学,帮忙导出了服务的jvm文件,进行分析,因为压测时间比较久,更容易发现问题,于是神器来了
MAT内存分析工具
MAT工具下载和使用说明
mat工具是之前eclipse上分析内存的,挺好用的,于是下载了,但是发现新的要依赖jdk11以上的版本,jdk11下载太慢,我就在eclips里面安装了mat工具插件。先上导出的dump文件分析结果
com.mysql.cj.jdbc.NonRegisteringDriver$ConnectionPhantomReference, loaded by org.springframework.boot.loader.LaunchedURLClassLoader @ 0x6f32007d8 occupy 23,138,000 (15.26%) bytes.
通过查看问题1和2的Details发现,是com.mysql.cj.jdbc.NonRegisteringDriver对象以及他内部引用的String类型的配置没有释放导致的。项目都是采用mybatis注解的方式,不存在手动创建连接和释放的问题,为啥会连接没有断开呢?
然后通过JProfiler的类追踪器,追踪这个对象,发现这个对象确实增加了,且不会释放掉
查询资料,找到一篇比较合理的解释是:
每次新建一个数据库连接,都会把该连接放入connectionPhantomRefs集合中。数据连接在空闲时间超过idleTimeout或生存时间超过maxLifetime后会被废弃,在connectionPhantomRefs集合中等待回收。因为连接资源一般存活时间比较久,经过多次Young GC,一般都能存活到老年代。如果这个数据库连接对象本身在老年代,connectionPhantomRefs中的元素就会一直堆积,直到下次 full gc。如果等到full gc 的时候connectionPhantomRefs集合的元素非常多,该次full gc就会非常耗时。
到这里发现了:
第一个存在的问题
数据库的连接池配置问题
数据库的连接池配置问题,我的修改方案是采用默认的配置,minimum-idle和maximum-pool-size都是10可以避免,回收连接带来的内存上升问题,这个其实也不算问题,因为full gc会回收掉这部分内存的。
详细分析说明链接
发布压测环境,再次压测,查看是否不是好了
压测的结果是,内存依旧在增长,只是相比较之前的趋势变小了。
说明问题还是得到了修复,只是没有完全修复,还有其他对象存在内存问题。
class org.apache.skywalking.apm.dependencies.net.bytebuddy.dynamic.Nexus @ 0x6f324aff8 - 6,954,504 (7.34%) bytes.
到这里,发现了
第二个存在的问题
skywalking的版本问题
详细分析说明链接
如果Http URI中是动态的绑定参数,就会导致每次都会当做一个新的 Endpoint,进而导致内存不断增长。
EndpointNameDictionary 这个类是使用枚举单例模式,GC Roots 会一直引用 endpointDictionnary 私有成员变量,进入老年代后,即使发生了 CMS GC,也不会回收这个对象。
但是该问题在 Spring MVC 框架中是没有问题的,Agent 插件中已经对这种情况做了优化,Skywalking Agent 的当时版本没有考虑到 RestTemplate 这个模板的问题。
如果使用了Spring Cloud 框架,可以使用 Feign。我们系统代码也比较早了,没有使用 Feign 去调用。
官方已经在6.7的版本进行了优化,所以更新了skywalking的版本后,问题得到改善,重新部署了压测环境,进行了新一轮的压测,内存已经看不见上升的趋势了
总结
内存泄漏问题定位是一个循序渐进的过程,需要认真分析同时补充对应的知识,主要还是依赖MAT工具的强大,可以分析到有问题的对象,记录这个日志是为了下一次分析时候,不忘记定位的过程以及工具的使用。