试着搞懂OOM?

2021-01-17  本文已影响0人  程序员点点

"我的代码OOM了,怎么办?"

"报什么错?"

"OOM啊,java.lang.OutOfMemoryError"

"……"

OOM大约是Java程序员绕不过去的梗,但OOM应该怎么排查呢?

什么是OOM

OOM大家都知道,是内存溢出,通俗的说,就是程序运行需要的内存,虚拟机给不了,所以程序就撂挑子不干了。

OOM产生原因

1- 内存分配不足——JVM启动参数指定的内存大小不够
2- 理论上分配够了,但是程序使用内存超预期——内存泄漏|溢出

内存泄漏:程序已经使用过的内存没有得到回收,此时这部分内存不能分配给其他人但又不被使用。
内存溢出:程序需要的内存超过了虚拟机能提供的内存大小

OOM类型

OOM类型有很多种,针对不同类型可使用不同的排查手段

java.lang.OutOfMemoryError:Java heap space

最常见的OOM类型之一了,堆空间不足,举个栗子,程序运行需要20M,但是实际上JVM只提供了10M

举个栗子:

以下代码执行时设置JVM参数:-Xms10m -Xmx10m

-Xms 初始堆内存 10M

-Xmx 最大堆内存10M

public static void main(String[] args) {
    String str = "黄河之水天上来";
    while (true){
        //循环创建字符串对象
        str += str + new Random().nextInt(999999999);
        //从常量池中获取字符串,若不存在,则创建一个字符串放到常量池中
        str.intern();
    }
}

运行结果:


image

java.lang.OutOfMemoryError:GC overhead limit exceeded

这个异常是指GC回收时间过长,时间过多耗费在GC中,但是回收效果不佳。

回收过长指的是连续多次超过98%的时间用来做GC,但是只回收了不到2%的堆内存,这样即使在GC清理后的内存也会很快再次填满,迫使GC再次执行,就此形成恶性循环,所以需要抛出异常

再举个栗子:

设置JVM参数:-Xms10m -Xmx20m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m

PrintGCDetails 打印详细GC信息

MaxDirectMemorySize 当Direct ByteBuffer分配的堆外内存到达指定大小后,即触发Full GC

public static void gcOverHead() {
    int i = 0;
    List<String> list = new ArrayList<>();
    try {
        while (true){
            list.add(String.valueOf(++i).intern());
        }
    }catch (Throwable e){
        e.printStackTrace();
        throw e;
    }
}

运行结果


image

java.lang.OutOfMemoryError: Direct buffer memory

直接内存溢出

原因分析:

直接内存崩溃,此处元空间并不在虚拟机中,而是使用本地内存,与GC无关。

常见于NIO程序中,使用ByteBuffer来读取和写入数据,这是基于通道channel和缓冲区buffer的IO方式,可以使用Native函数直接分配堆外内存,通过存储在JAVA堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。该方式在某些常见中能提高性能,因为避免了java堆和native堆中来回拷贝数据

还是举个栗子:

设置JVM参数:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m

public static void directBufferMemory() {
    System.out.println("本地内存 = " + (VM.maxDirectMemory() / 1024 / 1024) + "MB");
    try{
        TimeUnit.SECONDS.sleep(3);
    }catch (InterruptedException e){
        e.printStackTrace();
    }
    //allocateDirect 分配直接内存
    ByteBuffer.allocateDirect(6 * 1024 * 1024);
}

运行结果:


image
上一篇下一篇

猜你喜欢

热点阅读