JVM常见的OOM异常

157 阅读2分钟

1.前置

内存溢出的三种类型:

1.第一种OutOfMemoryError:PermGen space,发生这种问题的原意是程序中使用了大量的jar或class。

2.第二种OutOfMemoryError:Java heap space,发生这种问题的原因是java虚拟机创建的对象太多。

3.第三种OutOfMemoryError:unable to create new native thread,创建线程数量太多,占用内存过大。

2.正文

常见的OOM异常及复现方法:

在jvm没有足够内存为新创建的对象分配空间,并且没有足够内存为垃圾收集器使用时就会触发,java应用就会触发OOM。当然,linux本身也有OOM killer机制,当内核监控到进程占用空间过大时,尤其是内存瞬间增大时,为了防止耗尽内存,会触发OOM杀死进程。Java中常见的OOM如下:

(1)java.lang.OutOfMemoryError: Java heap space

这个异常的原因为内存泄漏或者内存溢出。内存溢出的时候,需要调整JVM参数-Xmx配置,调大堆空间,如果是内存泄漏,就需要找出泄漏的代码。

下面代码是一段典型的内存泄漏的代码,启动时设置-Xmx512m
public class JavaHeapSize {
    public static void main(String[] args){
        List<User> list = new ArrayList<>();
        User user = new User();
        while (true){
            list.add(user);
        }
    }
}

执行后等待一段时间:
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:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at boot.oom.HeapSize.main(JavaHeapSize.java:18)

(2)java.lang.OutOfMemoryError: GC overhead limit exceeded

这种异常的原因是垃圾收集器GC效率很低,jvm花费超过 98%的 CPU 时间来进行一次 GC,但是回收的内存却少于 2%的堆空间大小,并且GC连续超过5次都这样

public class JavaGcOverrhead {
    public static void main(String[] args){
        Map map = System.getProperties();
        Random random = new Random();
        while (true) {
            map.put(random.nextInt(), "value");
        }
    }
}

上面代码启动时加参数:-Xmx45m -XX:+UseParallelGC -XX:+PrintGCDetails运行一段时间,就会出现以下异常,注意:参数需要根据自己环境相应修改

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.Hashtable.addEntry(Hashtable.java:435)
at java.util.Hashtable.put(Hashtable.java:476)
at boot.oom.HeapSize.main(HeapSize.java:20)

通过增加参数-XX:-UseGCOverheadLimit可以避免这个异常,但其实是自己骗自己,还是需要实际去定位解决问题。

(3)java.lang.OutOfMemoryError: Requested array size exceeds VM limit

这个异常很容易理解,请求分配的数组大小超过jvm限制,出现这种情况的原因有2个: 请求分配的数组太大,导致jvm空间不足 请求的数组大于等于Integer.MAX_INT - 1 如下2段代码:

这段代码分配的数组太大,直接抛出Requested array size exceeds VM limit
int[] array = new int[Integer.MAX_VALUE - 1];
这段代码先抛出 Java heap space后再抛出Requested array size exceeds VM limit
for (int i = 3; i >= 0; i--) {
    try {
        int[] array = new int[Integer.MAX_VALUE-i];
        System.out.format("Successfully initialized an array with %,d elements.\n", Integer.MAX_VALUE-i);
    } catch (Throwable t) {
        t.printStackTrace();
    }
}
结果如下:
java.lang.OutOfMemoryError: Java heap space
at boot.oom.ArraySizeExceeds.main(ArraySizeExceeds.java:12)
java.lang.OutOfMemoryError: Java heap space
at boot.oom.ArraySizeExceeds.main(ArraySizeExceeds.java:12)
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
at boot.oom.ArraySizeExceeds.main(ArraySizeExceeds.java:12)
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
at boot.oom.ArraySizeExceeds.main(ArraySizeExceeds.java:12)

(4)java.lang.OutOfMemoryError: MetaSpace

这个异常是元空间不足,解决办法是加大元空间大小,配置参数MaxMetaSpaceSize,在启动引用时加入参数: -XX:MaxMetaspaceSize=2m,

直接报错: Error occurred during initialization of VM OutOfMemoryError: Metaspace

(5)java.lang.OutOfMemoryError: Request size bytes for reason. Out of swap space

这个异常是操作系统的swap空间不够引起的。我们知道jvm分配的最大内存由Xmx等一些参数指定,如果jvm需要的总内存超出了宿主机可以分配的最大的物理内存,就会用到swap space,如果swap space不足,jvm内存分配就会失败,从而抛出这个异常。这个异常的定位比较复杂,有可能是宿主机上面的其他进程耗用内存太多导致。所以不建议用增加swap space这种粗暴的方式,禁用swap,做好进程的隔离是比较妥当的解决方案。