什么是OOM(OutOfMemoryError)?它是如何产生的?如何避免OOM?代码举例讲解

OOM(OutOfMemoryError)是Java运行时产生的内存溢出错误。它会在JVM在试图分配内存的时候产生,提示JVM内存空间无法满足需求。

OOM产生的主要原因有:

  1. 堆内存溢出:当对象实例数量过多导致堆内存不足时产生。常见场景是大量对象被保存在集合中没有及时释放。
  2. 栈内存溢出:当线程数量过多或递归调用层数过深导致栈内存耗尽时产生。
  3. 方法区溢出:当加载过多类或常量池内容过多导致方法区内存不足时产生。常见场景是大量动态代理类的产生。
  4. 本地内存溢出:当本地内存任意使用(如IO操作)导致本地内存耗尽时产生。

来看一个简单例子:我们可以编写几个Java程序模拟这几种OOM的产生,以便加深理解。

堆内存溢出:

public class HeapOOM {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        while (true) {
            list.add(new Object());
        }
    }
}

栈内存溢出:

public class StackOOM {
    public void recursiveCall() {
        recursiveCall();
    }

    public static void main(String[] args) {
        StackOOM oom = new StackOOM();
        oom.recursiveCall();
    }  
}

方法区溢出:

public class MethodAreaOOM {
    public static void main(String[] args) { 
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(MethodAreaOOM.class);
            enhancer.setUseCache(false);
            enhancer.create();
        }
    }
}

本地内存溢出:

public class NativeOOM {
    public static void main(String[] args) throws Exception {
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024*1024*1024);
        while (true) {  
            buffer.put(new byte[1024*1024]);
        }
    }
}

要避免OOM的发生,我们需要:

  1. 及时释放不再使用的对象引用。
  2. 避免过度嵌套递归调用,保持栈桢数在适当范围。
  3. 避免动态创建过多类,特别是动态代理类。
  4. 避免直接内存分配过量,可以使用堆内存作为替代。
  5. 合理配置JVM内存参数,特别是堆内存和栈内存的大小。