试着搞懂OOM?

 2023-01-29
原文作者:程序员点点 原文地址:https://juejin.cn/post/6918732897688125448

"我的代码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();
        }
    }

运行结果:

202301011644320051.png

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;
        }
    }

运行结果

202301011644326902.png

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);
    }

运行结果:

202301011644333623.png