环境
JDK8

准备工作
先准备好一个OOM程序:(程序是网上随便找的)

import java.util.ArrayList;

public class TestOOM {

    static class OOMObject {
        private String content;

        public OOMObject(String content){
            this.content = content;
        }
    }

    public static void main(String[] args) {
        ArrayList<OOMObject> list = new ArrayList<>();
        int i = 0;
        while(true){
            list.add(new OOMObject(i+++""));
        }
    }
}

我们来使用以下jvm命令启动:-Xms2m -Xmx4m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=C:\Users\12340\Desktop
含义如下:

-Xms 设置JVM启动时堆内存的初始化大小;

-Xmx 设置堆内存最大值;

-XX:+HeapDumpOnOutOfMemoryError 表示出现OOM异常时,生成堆快照hprof文件;

-XX:HeapDumpPath=C:\Users\12340\Desktop设置堆快照hprof文件存储的位置。

这里读者可以根据自己的需要去调整。但请注意要放到有权限的文件夹,别放到需要管理员权限才能访问的文件夹,不然Java没有这个权限无法去操作。

这里补充一个小tip:

如果你使用的是Intellij IDEA进行开发,可以很方便地在这里修改JVM启动参数:

JVM指标监控告警样例_Java


点击这里的Edit Configurations

JVM指标监控告警样例_JVM指标监控告警样例_02


在VM options栏里填入我们的JVM参数即可。

最后运行我们的代码,会显示如下:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to C:\Users\12340\Desktop\java_pid14300.hprof …
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:265)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
at java.util.ArrayList.add(ArrayList.java:462)
at TestOOM.main(TestOOM.java:17)
Heap dump file created [4755536 bytes in 0.030 secs]

此时已发生OOM并使进程异常退出。
并且通过Heap dump file created [4755536 bytes in 0.030 secs]可以看到堆文件已经生成。
针对两个问题,我们开始排查:

OOM发生在哪个线程哪行代码

是什么实体类型过多导致OOM

排查

使用JVisualVM排查

JDK8提供了一个可视化工具Jvisualvm,在jdk的bin目录下可以找到。

双击打开它:

JVM指标监控告警样例_ci_03


点击左上角的“装入快照”按钮。

JVM指标监控告警样例_Java_04


把这里的文件类型更改为*.hporf类型,然后选择我们刚才那个代码导出的hprof文件,选择打开。

我们可以看到在概述里已经提示我们发生了OOM异常:

JVM指标监控告警样例_ci_05


击红色框里的main。

在这里我们首先可以看到,是在代码的第17行操作时发生了OOM:

JVM指标监控告警样例_ci_06


那么具体导致内存溢出的是什么对象呢?

我们点开红框下面的Java.util.ArrayList#9看看:

JVM指标监控告警样例_JVM指标监控告警样例_07


在size这一栏可以看到,我们这个ArrayList中存储的元素个数达到了三万多。那么这些元素对象分别都是什么呢?展开我们的elementData看看:

JVM指标监控告警样例_Java_08


我们可以发现全都是TestOOM下的OOMObject对象。那OOM是否真的就是因为这个对象太多呢?到目前其实已经基本可以猜到了,但是还不能完全确认,我们可以再来看看类视图。

JVM指标监控告警样例_ci_09


在类视图中,我们也可以发现OOMObject对象确实很多,占用了大量的堆内存。

总结可以得到:我们创建了太多无法回收的OOMObject对象,所以导致了OOM。
总结
排查OOM问题时,总体可分为如下几步:

生成堆转储快照dump文件(或者称hprof文件);
利用相关工具分析堆转储快照(本篇中使用的是JVisualVM可视化工具);
在线程快照(堆转储上的线程)中定位到发生OOM的线程和代码位置;
综合分析线程快照里提示的本地变量信息及类视图中的实例占比,找出导致OOM的实体类型。