查找内存溢出的原因
一、内存溢出与内存泄漏
内存溢出是指程序在申请内存时,没有足够的内存空间供其使用。
内存泄露是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光
二、内存溢出定位相关方法
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地址
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的发生频次是突变,还是渐变,最终确定问题原因。