问题缘由:
某个项目调用我们的接口一直超时
问题表现:
老年代内存在20多分钟内突然急剧上涨,从300M涨到最大值512M,JVM频繁GC,GC暂停时间长,业务逻辑无法得到正常运行,大约5分钟后服务健康检查不通过,实例被自动重启。
问题发生频率:
近期每周都有1-2次
问题分析:
一、查看日志,线程数,请求并发量,网络io等无明显异常
二、dump文件分析
- 通过“Eclipse Memory Analyzer”(简称MAT)工具打开dump文件
内存泄露这边就有个可疑的类,有10519个实例
2、接着点击histogram,根据retainHeap排序,同样能发现这个类
3、这个类是怎么存储对象的。右键点击该类,在弹出菜单中按下图中选择对应菜单:
看到这个类有个属性是archives,是一个LinkedList,数量为10455,与第1张图的10577接近。同时,在size>0的情况下,为什么first=null?
4、翻看archievs相关的操作代码,它对象类型是LinkedList,属于线程不安全,在多线程的操作下却未有锁保护,猜测极有可能造成内部数据混乱,即:size > 0,但first == null。
5、翻看LinkedList的remove方法代码,发现如果first == null 是会报错的,而我们的业务代码直接吃掉了这个异常后返回,实际上压根就没有删除,这就是为什么内存会突涨的根本原因!
解决方案
用Collections.synchronizedList包装一下即可变成线程安全:
List<RequestRecord> archives = Collections.synchronizedList(new LinkedList<>());