1 背景
以sc-pacific-qi-service OOM作为分析案例
问题现象:set-gh-service-kaeru-csc-pacific-qi-service02 cpu load及频繁fullgc告警。
分析思路:先去raptor查看当前机器的使用情况,cpu load,对cms,jvm内存情况。
2 Raptor分析
2.1 观察gc次数
如下图左侧,fullgccount很稳定,每分钟发生7-8次,曲线非常均匀。根据这个图基本可以确认内存泄漏了,而且每次回收一点也没有回收掉。但是哪个区域泄漏,还需要其他的数据分析
2.2 观察jvm内存区域的gc情况
eden区域曲线很均匀,metaspace区域很稳定,在安全范围,所以可以排除元数据区域oom。
oldgen的区域内存一致持续不变,而另外一台机器的内存是持续在增长,结合上面的gc在频繁发生,可以分析,产生了old区发生了内存泄漏
2.3 MAT分析
2.3.1 MAT官方文档
https://help.eclipse.org/2021-06/index.jsp?topic=/org.eclipse.mat.ui.help/welcome.html
2.3.2 Getting Started Wizard
2.3.2.1 Leak Suspects 内存泄露报表
自动检查可能存在内存泄露的对象,通过报表展示存活的对象以及为什么他们没有被垃圾收集;
2.3.2.2 Component Report对象报表
对可疑对象的分析,如字符串是否定义重了,空的collection、finalizer以及弱引用等
2.3.3 OverView
打开一个dump文件后一般会先选择leak suspect界面。overview界面会以饼图的方式显示当前消耗内存最多的几类对象,可以使我们对当前内存消耗有一个直观的印象。但是,除非你的程序内存泄漏特别明显或者你正好在生成hprof文件之前复现了程序的内存泄漏场景,你才可能通过这个界面猜到程序出问题的地方,一句话就是比较看运气。
查看下图很直观,DefaultSqlSessionFactory很可能发生了内存泄漏
2.3.4 Histogram
2.3.4.1 shallow heap
指的是某一个对象所占内存大小。
2.3.4.2 retained heap
指的是一个对象的retained set所包含对象所占内存的总大小。
retained set指的是这个对象本身和他持有引用的对象和这些对象的retained set所占内存大小的总和,用官方的一张图来解释如下:
Lists number of instances per class
查看下图,Long的对象有1099w个,占用263M空间
Char数组对象有29w个,占用961M
2.3.5 Dominator Tree
List the biggest objects and what they keep alive.
基本可以定位是DefaultSqlSessionFactory下的PageInterceptor下的GuavaCache出了问题
2.3.6 实操分析
①先定位GuavaCache,然后找到对应的put代码的引用
②找到实际缓存的容器msCountMap
②结合mat的分析结果,找到map的key和value与目标完全一致,基本定位是msCountMap.put这块代码有问题
问题原因是因为统计代码的Sql太大,每条sql有7.4M,总共有170 * 7.4M = 1.2G,与问题现象一致。可以定位是该位置问题导致
③深入分析每条缓存为什么占用这么大的空间
是因为CacheKey比较大,具体就是其属性sql比较大,sql大是因为参数多,总共有7.1w个参数
④查看CacheKey的数据结构,注意查看updateList
⑤查看CacheKey创建过程
⑥查看实际测试结果
2.3.7 问题原因
在分页查询的时候,会缓存执行总条数,因为使用了CacheKey做Key,所以对于不同的SQL,如sql参数数量、顺序等等,都不会命中,而且是永久性缓存,所以导致内存被占用无法回收
2.4 解决方法
2.4.1 根本原因
guava默认缓存1000条数据,假如没有配置过期时间,则永不过期,这点和GC后内存占用是一致的。
2.4.2 修复方法
增加缓存最大数量限制,问题解决
样例: