内存溢出
一、问题表现
凌晨6点半,线上服务器内存报警!
二、排错
因为报警第一时间未能及时查看服务器,需要注意时间差。
首先执行top命令,然后shift+m,按内存进行排序:
如果是第一时间查看,能够发现内存占用较高的程序,因为隔了一顿时间,只能对内存占用前几的项目进行排查;
线上项目添加了Jvm参数,打印GC日志,以及保存内存溢出时的堆栈信息;
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/app/tomcat1/dump
查看线上项目dump文件保存的文件夹,第一时间定位了问题项目;将dump文件压缩后复制到本地,通过jprofile进行分析:
堆中一共是用了2G内存,排名前三的对象的是char[],String,HashMap$Node,占用了90%的内存;可以预想,内存中生成了将近20万个HashMap对象,而这20万个HashMap对象中持有了840万个节点,而这些节点中,主要保存的是String类型的对象。通过Retained Size,还可以发现,1万个ArrayList对象保留了将近2个G的内存,基本可以确定,内存中的HashMap或者是ArrayList,导致了内存溢出,HashMap和ArrayList应该是相互持有关系,具体是什么,需要进一步分析;
查看HashMap和ArrayList的最大对象:
看到这,大概就能直到问题所在了,ArrayList中排名前三的对象将近占用了800M,这就是问题所在,那么这些个ArrayList是哪里产生的呢?
通过Graph可以看到对象的持有以及方法的调用,栈中的引用持有的对象,通过Controller对象调用的,这明显就是一个请求调用;
查看tomcat日志,查看发生内存溢出时间段的请求:
可以看到,这个时间,有同时的5个相同请求,与上文分析一致;
结合代码,查看具体问题:发现该方法主要为ajax提供数据,正常会传入新闻id的参数;但是该请求并没有,请求通过pojo对象接收参数,而且没有做参数为空的处理, 导致参数直接用的int类型的默认值0,因为内部根据传入的id递归的去查找相关的新闻id,0正好是所有类型的父id,导致该请求加载了所有的新闻内容,并且没有做分页处理,导致了内存溢出。
三、优化
第一、对于无参情况做了相对应的判断;第二、做了分页处理,避免一次加载太多数据;