查找内存溢出的原因

一、内存溢出与内存泄漏

内存溢出是指程序在申请内存时,没有足够的内存空间供其使用。

内存泄露是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光

二、内存溢出定位相关方法

1.获取Java虚拟机内存快照

1.1.主动获取内存快照

#查看当前java进程 >jps 

#查看系统GC情况统计(jstat -gc 进程ID 持续监控n毫秒 打印次数) >jstat -gc 2780 1000 3 

S0C: 第一个Survivor区域大小,S1C:第二个Survivor区域的大小

S0U:第一个Survivor区域使用的大小,S1U:第二个Survivor区域使用的大小

EC:Eden区域的大小,EU:Eden区域的使用大小

OC:Old区的大小,OU:Old区使用的大小

MC:方法区大小

YGC:年轻代垃圾回收次数,YGCT: 使用时间

FGC:年老代垃圾回收次数,FGCT: 使用时间

GCT:垃圾回收总耗时

#通过jmap导出堆内存使用情况的dump.dat文件 jmap -dump:format=b,file=C:/demo/jvm/dump.dat 2780 

注意:生产环境内存一般很大,不能用这个方式导出。占用大量资源影响系统正常运行。

1.2.设置java虚拟机参数,内存溢出时,保留内存快照

#示例设置虚拟机最大内存30m -Xmx30m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=C:/demo/jvm/dump.dat 

设置打印gc日志的参数

#设置保存GC日志,注意,需要设置日志文件个数(需大于1个),和文件大小(需大于8k) -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:C:/demo/jvm/log/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=286k 

注意:Windows环境下测试时,jvm参数中用到的文件路径,不能自动创建,需要提前创建好。

2.查看生成的内存快照

2.1.用java的jhat命令查看生成的dump

jhat -port 8081 C:/demo/jvm/dump.dat 

2.1.1.打开查看dump地址

http://localhost:8081,如下图:

2.1.2.查看各个类的内存占用情况

点击Show heap histogram,查看实例个数及占用内存情况,如下图:

点击Execute Object Query Language (OQL) query,打开查询工具,参考OQL Help写查询语句定位目标类

2.2.用Eclipse Memory Analyzer工具查看生成的dump

2.2.1.下载与安装

下载地址:https://www.eclipse.org/mat/downloads.php

注意,选择与被监控的jre/jdk对应的版本下载

在Eclipse初始化文件MemoryAnalyzer.ini配置javaw.exe路径,然后才能启动。

-vm C:\Program Files (x86)\Java\jdk1.8.0_141\bin\javaw.exe 

2.2.2.打开生成的dump文件

A.查看快照生成时的内存使用情况

B.查看内存中占用空间较大的对象

C.查看大量占用内存的对象间的引用关系

D.定位大量占用内存的对象所在代码行

3.解读思路

实际生成中不会像上图中那样只有一个明显的大对象。可能会是一堆大小差不多的对象将内存撑爆的。定位问题还是很有难度的。

个人认为,应该从内存溢出和内存泄漏两方面去区分导致内存溢出的原因。如:突如其来的某业务的大量访问造成溢出的情况。还是每隔一段时间就会发生溢出(长期泄漏)的情况。结合上面定位的代码行划出业务范围,分析GC日志,看看FullGC的发生频次是突变,还是渐变,最终确定问题原因。