堆内存溢出
在Java内存区域的
Java堆
章节介绍过,如果Java堆中没有内存完成新实例分配,且无法再扩展(通过-Xmx指定了上限或受限于物理内存),会抛出OutOfMemoryError异常。
Java堆用于存储对象实例,我们只要不断的创建对象,并保证GC Roots到对象之间有可达路径来避免垃圾回收器清除这些对象,那么随着对象数量不断增加,总容量触及堆的容量限制后就会产生内存溢出异常。
堆内存溢出示例
示例采用Win10下的JDK 8运行,不同的JDK版本可能会有不同的结果。版本信息如下:
示例代码如下:
package jvm.oom;
/**
* 虚拟机参数:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/dump/ -verbose:gc
*
* @author faith.huan 2020-01-06 22:02
*/
public class JavaHeapOom {
private static final int ONE_MB = 1024 * 1024;
public static void main(String[] args) {
byte[][] bytes = new byte[10][];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = new byte[ONE_MB];
System.out.println("创建第" + (i + 1) + "个1M字节数组成功");
}
}
}
示例参数解释:
参数 | 解释 |
---|---|
-Xms | 设置Java堆初始大小 ,默认单位b,可用单位:K ,k ,M ,m ,G ,g ,本例中-Xms10m 表示堆初始空间10MB |
-Xmx | 设置Java堆最大大小 ,默认单位b,可用单位:K ,k ,M ,m ,G ,g ,本例中-Xmx10m 表示堆最大空间10MB |
-XX:+HeapDumpOnOutOfMemoryError | 当发生内存溢出时使用heap profiler (HPROF) 将Java堆记录到当前路径 文件中,+HeapDumpOnOutOfMemoryError 表示激活此设置即记录到文件,-HeapDumpOnOutOfMemoryError 表示不激活此设置即不记录到文件,默认为不激活。 |
-XX:HeapDumpPath | 指定激活HeapDumpOnOutOfMemoryError 选项时Java堆记录的文件路径及名称,默认为当前路径 -Idea运行时的Working directory ,文件名为java_pid pid.hprof ,pid表示进程id。-XX:HeapDumpPath=D:/dump/ 表示记录文件到D:/dump/目录下 |
-verbose:gc | 显示垃圾收集日志信息. |
IDEA运行参数截图
程序运行日志
创建第1个1M字节数组成功
创建第2个1M字节数组成功
创建第3个1M字节数组成功
创建第4个1M字节数组成功
创建第5个1M字节数组成功
创建第6个1M字节数组成功
[GC (Allocation Failure) [PSYoungGen: 1912K->496K(2560K)] 8056K->6945K(9728K), 0.0007925 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
创建第7个1M字节数组成功
[GC (Allocation Failure) --[PSYoungGen: 1583K->1583K(2560K)] 8033K->8041K(9728K), 0.0009478 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 1583K->1445K(2560K)] [ParOldGen: 6457K->6425K(7168K)] 8041K->7871K(9728K), [Metaspace: 3133K->3133K(1056768K)], 0.0041284 secs] [Times: user=0.13 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) --[PSYoungGen: 1445K->1445K(2560K)] 7871K->7911K(9728K), 0.0008741 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 1445K->1409K(2560K)] [ParOldGen: 6465K->6443K(7168K)] 7911K->7853K(9728K), [Metaspace: 3133K->3133K(1056768K)], 0.0038629 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to D:/dump/\java_pid11336.hprof ...
Heap dump file created [8880160 bytes in 0.012 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at jvm.oom.JavaHeapOom.main(JavaHeapOom.java:15)
Heap
PSYoungGen total 2560K, used 1595K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 77% used [0x00000000ffd00000,0x00000000ffe8ee18,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 6443K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 89% used [0x00000000ff600000,0x00000000ffc4ad08,0x00000000ffd00000)
Metaspace used 3240K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 351K, capacity 388K, committed 512K, reserved 1048576K
通过上面的日志可以看出,共创建了7个1M的字节数组,在创建第8个时,发生内存溢出。内存溢出时堆内存情况如下
区域名称 | 总内存 | 已用内存 | 使用比例 | 剩余内存 |
---|---|---|---|---|
eden space 伊甸区 | 2048K | 1595K | 77% | 453K |
from space 幸存区1 | 512K | 0K | 0% | 512K |
to space 幸存区2 | 512K | 0K | 0% | 512K |
OldGen 老年代 | 7168K | 6443K | 89% | 725K |
从上面表格中可见,年轻代(eden+from survior+to survior),老年代剩余空间都不足1M,无法容纳第八个1M的字节数组,且前面7个又无法进行回收,而且设置了堆上限10M,因此发生内存溢出异常。
堆栈溢出分析
使用Eclipse Memory Analyzer
对上面例子发生OOM时导出的堆快照文件java_pid11336.hprof
进行分析。
Eclipse Memory Analyzer
下载地址:https://www.eclipse.org/mat/downloads.php,根据自己的操作系统选择合适的版本,此处我选择Win64版本
下载完成后,进行解压,进入mat
目录,双击MemoryAnalyzer.exe
启动软件。
依次点击File
->Open Heap Dump
选择上面示例产生的java_pid11336.hprof
文件,然后点击打开
按钮
勾选Leak Suspects Report
(内存泄漏报告),点击Finish
报告显示找到一个可疑问题,即主线程持有了7M的bytes[][]对象。
点击报告中的See stacktrace
连接可疑查看堆栈信息,定位到具体的代码,本示例点击后定位到JavaHeapOom.java的第15行,如下图所示