排查经过
在春节影院APP出现保障,出现接口偶发性的超时,一段时间正常,一段时间超时非常严重,最高达到了200s
并且越来越严重![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tWvhB9sV-1630729305168)(http://note.youdao.com/yws/res/7103/26AC7BF27BFA4BE6990A2BD4E374674C)]](https://img-blog.csdnimg.cn/d9a1cb17d5444a74814103efa2c844ad.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAV2VuZ3kuWHU=,size_20,color_FFFFFF,t_70,g_se,x_16)
cpu出现飙高,由于当时运维立刻重启没有保留现场,所以没有定性问题的依据,但当时通过观察Zabix 上下文切换数和中断数趋势出现不一致的情况(中断数陡增)
另外CPU飙高,网络流量没有明显增加,重启后恢复,基于以上几点怀疑是内存泄露。但由于没有数据支持,只能等待再次复现
果然过了2天CPU再次上升到了50%的告警,并出现了响应超时的情况,立刻上去拉了栈dump和堆dump,并通过jstat -gcutil 进行监控
发现full gc太频繁 且一次gc的时间大于4s,并且栈信息中始终包含了gc进程,且大部分tomcat的线程都处于wait-on-condition的状态(说明流量并不高)

并且再次出现了上下文切换数和中断数趋势出现不一致的情况![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q7yGO8aj-1630729305173)(http://note.youdao.com/yws/res/7121/467A49915CD040C7B2919142406D372F)]](https://img-blog.csdnimg.cn/1d27ae0c4e9f4104a897eacbcea691ea.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAV2VuZ3kuWHU=,size_20,color_FFFFFF,t_70,g_se,x_16)
由此基本可以断定是内存泄漏了,接下来就是要找出元凶了
首先通过jmap -histo:live 初步看了下![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JZkM08pG-1630729305174)(http://note.youdao.com/yws/res/7127/549A1DC7154E4B679F8B88AFEA0C0D43)]](https://img-blog.csdnimg.cn/918b3a7f5cc74850bd8b071d578b6be8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAV2VuZ3kuWHU=,size_20,color_FFFFFF,t_70,g_se,x_16)
发现ShowMerge对象特别的多,但这个对象在代码里面使用的地方特别多,也没有发现内存泄露的点
随后只能通过jprofile 进行堆分享,通过大对象分析,找到最大的实例,占用了将近6个G的数据
那肯定是这个引起的问题了,于是根据这个对象和其包含的子对象,发现问题对象是一个ehcache 的缓存对象,缓存的是CinemaMergeVo
每一个缓存元素(net.sf.ehcache.Element),将近1M,总共占据6000M,相当于缓存了6000个元素
根据这个线索,找到缓存的逻辑,终于发现问题所在

发现代码中缓存处理的逻辑有问题,处理逻辑,在APP首页加载时,会根据用户的城市和经纬度从数据库查询出该城市所有的影院以及其包含的场次,按距离排序以后,返回给APP,同时做了本地缓存,由于缓存的逻辑设置不合理,
1.首先缓存的key设置不合理,经纬度组合命中的概率太低了,即使在同一个位置,两个不同手机的经纬度都有偏差
2.其次缓存的是影院列表这个大对象(包含场次数据),经纬度有一点点变化,也需要重复缓存这个大对象
3.ehcache 缓存的失效实际设置的是1个小时,但ehcache,采用的是惰性删除,过期后缓存立即1删除
4.设置缓存上限不合理,设置的缓存上限为10万,但当缓存记录不到1万时,占用内存已经高达6G
优化方案
1.设置合理缓存条数上限
2.在尽量保持原有缓存逻辑的基础上,将缓存的大对象,改为影院Id的列表,单独缓存影院对象和场次对象(使用对象池Common Pool),以实现复用,大大降低了内存的占用