案列背景
最近在做公司项目核心接口压测和稳定性压测时,各接口的成功率不足99.99%,通过分割压测之后发现,在压测A服务时,内存上升明显,且伴有频繁的fgc发生
jvm老年代内存使用率已经达到了100%且应用的fgc有5000+次,fgc频率大概在2s左右,怀疑有内存泄漏问题产生
压测工具tps表现和现象
内存飙升导致频繁的fgc,导致在压测时出现tps不稳定现象,出现毛刺现象
Tps火焰图如下:
排查过程
内存dump,Mat分析
出现内存100%之后,将A服务的内存dump文件拿到之后,使用MAT工具分析,mat显示如下:
从MAT工具上显示,有大量的SessionImpl对象存在,怀疑内存泄漏发生在此处
源码追踪
通过跟踪源码,找到了InMemorySessionManager相关代码,并跟踪到了SessionImpl实例类代码如下:
调用处:
在哪里调用了InMemorySessionmanager#createSession方法?
此时发现有多出地方会调用该方法,无法确定是由哪里导致Session创建的
额外发现
通过使用jstat -gcutil 1 1000观察A服务的pod内存和gc情况,当压测一段时间之后停止压测,观察一段时间,发现pod的内存会逐步往下掉,最终内存会回退到正常范围,怀疑有SessionImpl过期清除机制。同时,在以上InMemorySessionManager代码中发现了如下方法:
此处会定期清理session对象。
跟踪整个调用栈:
SessionImpl#maxInactiveInterval--->InMemorySessionManager#defaultSessionTimeout=30*60
InMemorySessionManager提供了方法setDefaultSessionTimeout方法可以修改此处属性
在UndertowServletWebServerFactory中有关于session的超时设置
为此,查询springboot自动装配关于session过期的设置:
为了验证是否是由于该处设置引起的,增加关于session过期的设置,并且设置过期时间10s,再次压测:
此时,内存的上升速度没变,但是每次经过fgc
(cms垃圾收集器-XX:CMSInitiatingOccupancyFraction=70),内存回退到正常状态
SessionImpl是有哪里创建的
通过对比不同的服务之前的内存增长差异情况,发现之后A会出现如上内存快速增长情况。再结合session过期设置的验证效果,查询了A的业务代码发现如下代码:
其中@ModelAttribute注解使用在方法上时,会在每次调用Control的方法前都会调用,注意到:
this.session = request.getSession();跟踪代码,发现最终会调用到InMemorySessionManager#getSession方法创建大量的SessionImpl存入到Sessions(一个ConcurrentHashMap)
至此,内存泄漏的最终元凶找到了
再次验证
在A服务中,去掉对BaseController的继承,再次压测心跳接口,此时应用内存和gc恢复正常
总结
1 确定内存泄漏对象
出现内存泄漏问题,首先获取服务内存映射(当让也可以使用:(jmap -histo:live 1 |head -20查看内存大对象),然后查看内存类。
2 分析代码,查询资料,确定泄漏来源