一次内存溢出的填坑经历

在项目运行过程中,可能会出现内存溢出,内存溢出的原因多种多样,而在内存溢出后,我们如何查找和分析内存溢出的原因呢?这里来说一说我遇到的次遇到的内存溢出经历。

大致情况是这样的:应用在启动后,过一段时间(这个时间不确定),内存忽然爆满,然后频繁的YGC,一会过后,老年代爆满,然后是频繁的FGC,最终撑爆内存,抛出OOM。重启应用后,还是这个过程。

1、查看java进程的内存使用情况和GC情况

通过jastat工具查看GC情况,以及各generation占用比例:

jstat -gcutil  1000

通过观察分析是否是JVM的运行内存分配不合理,可通过新生代、老年代内存占用情况,以及GC情况观察得出。

如果FGC很频繁,说明老年代被迅速占用满,这有可能是老年代本身分配的内存太小,或者是内存中大量对象被引用,导致无法被YGC等等,最终这些对象都进入老年代导致老年代爆满。

要分析是什么原因造成的,可以获取内存溢出时JVM的内存快照,通过对快照的分析,来定位内存溢出的原因。

通过jmap查看JVM堆内存概况:

jmap -heap 

这个内存概况会根据使用的垃圾收集器的不同而有所差异。

2、获取jvmdump

获取jvmdump可通过添加应用服务器(如tomcat)启动参数:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/mikan/jvmdump

配置该参数后,在内存溢出时,JVM就会自动dump出当时的内存快照到HeapDumpPath指定的目录。

也可通过jmap在线获取内存dump,只是在使用jmap获取dump期间应用会被卡住,这可能对线上应用会有影响。如

jmap -J-d64 -dump:format=b,file=/home/mumq/jvmdump.hprof 

其中pid为java进程的ID。

获取到JVM内存快照后,可以使用相关的工具来对它进行分析。

3、分析jvmdump

jvmdump文件一般比较大(几个G),所以在做离线分析时,千万别在服务器上,因为在解析jvmdump时,会占用极大的内存,如果在服务器上,很可能影响线上应用的正常运行。所以一般情况是down到空闲的机器上做离线分析。

分析jvmdump的工具有很多,这里推荐使用的是Memory Analyzer (MAT),下载地址是:http://www.eclipse.org/mat/downloads.php。

当前最新Release版本是1.4.0,提供了两种版本:eclipse插件和独立版本,都可通过上面链接下载。

这里以独立版本为例。在开始分析dump文件之前,先设置MAT的内存,可根据分析的dump文件大小来设置,如果分析的dump文件为2G,那设置2G内存的话,那么解析dump文件生成report会很快,否则,等几个小时都不知道。。

如何设置?在与MAT相同目录下的MemoryAnalyzer.ini文件中添加如下配置:

-vmargs
-Xms2048m
-Xmx4096m

具体MAT的使用方法,可以直接查看MAT自带的示例教程,相对来说还是比较全面的。

在MAT解析完内存快照后,会生成相应的report,通过report可以看到当时内存使用情况、内存溢出的可疑之处(Leak Suspects)。如:

一次内存溢出的填坑经历_第1张图片

点击Leak Suspects,可以查看到具体的详细信息:

一次内存溢出的填坑经历_第2张图片

点击detail,查看详细信息,通过 Accumulated Objects by Class in Dominator Tree列表可以看到:

一次内存溢出的填坑经历_第3张图片

User对象有10W+,怎么会这样呢?通过 Accumulated Objects in Dominator Tree列表可以看到:

一次内存溢出的填坑经历_第4张图片

是调用了UserServiceImpl.login方法,一次性查询出了10W+的用户数据。如果频繁的调用该方法,则会很快的导致OOM。再查看代码发现在调用login方法时,如果用户名参数传null时,会查询出所有用户名为null的用户。这在数据量还很小或者请求量不大的时候,可能不会有什么问题,一旦数据量上来了,或请求量上来了,那很快就会出现OOM。

找到问题原因,如何解决就容易了。所以在写代码的时候如果考虑不周全,就有可能导致这种OOM的bug。

血的教训啊。

你可能感兴趣的:(JVM)