jvm内存溢出异常 🤔

213 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

泄漏与溢出

  • 内存泄露Memory Leak: 申请的内存空间没有及时释放,导致后续程序里这块内容永远被占用。
  • 内存溢出Out Of Memory: 要求的内存超过了系统所能提供的,内存需求不能被满足

一般情况下大家可能都感受不到内存泄漏的存在,真正有危害的是内存泄漏的堆积,这会最终消耗尽系统的所有内存,导致内存溢出。

运行时数据区域的常见异常

在JVM中,除了程序计数器外,虚拟机内存的其他几个运行时数据区域都有发生OOM异常的可能。

堆内存溢出

不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象。

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

虚拟机栈和本地方法栈溢出

单个线程下不断扩大栈的深度引起栈溢出。

public class StackSOF {
    private int stackLength = 1;
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }
    public static void main(String[] args) {
        StackSOF sof = new StackSOF();
        try {
            sof.stackLeak();
        } catch (Throwable e) {
            System.out.println("Stack Length: " + sof.stackLength);
            throw e;
        }
    }
}

循环的创建线程,达到最大栈容量。

创建线程导致内存溢出 (注意:该代码创建线程是映射到操作系统内核线程,请不要尝试运行!!!)

public class StackOOM {
    private void dontStop() {
        while (true) {
        }
    }
    public void stackLeadByThread() {
        while (true) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }
    public static void main(String[] args) {
        StackOOM stackOOM = new StackOOM();
        stackOOM.stackLeadByThread();
    }
}

运行时常量池溢出

不断的在常量池中新建String,并且保持引用不释放。

运行时常量池溢出 java.lang.OutOfMemoryError: Java heap space(JDK6及以前将是 java.lang.OutOfMemoryError: PermGen space)

public class MethodAreaOOM {
    static class ObjectInMethod {
    }
    public static void main(final String[] args) {
        // 借助CGLib实现
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(ObjectInMethod.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invokeSuper(o, objects);
                }
            });
            enhancer.create();
        }
    }
}
// *注:默认情况报GC overhead limit exceeded,通过-XX:-UseGCOverheadLimit参数禁用保护机制看到实际错误信息

方法区溢出(JDK7及以前)

代码1 借助CGLib直接操作字节码运行时产生大量的动态类,最终撑爆内存导致方法区溢出。

方法区溢出 java.lang.OutOfMemoryError: PermGen space

public class MethodAreaOOM {
    static class ObjectInMethod {
    }
    public static void main(final String[] args) {
        // 借助CGLib实现
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(ObjectInMethod.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invokeSuper(o, objects);
                }
            });
            enhancer.create();
        }
    }
}
// *注:默认情况报GC overhead limit exceeded,通过-XX:-UseGCOverheadLimit参数禁用保护机制看到实际错误信息

元空间溢出(JDK 8)

代码同代码1,借助CG Lib运行时产生大量动态类,唯一的区别在于运行环境修改为Java 1.8,设置-XX:MaxMetaspaceSize参数,便可以收获java.lang.OutOfMemoryError: Metaspace这一报错,2.2.4和2.2.5的对比也验证了JDK 8中元空间取代永久代这一事实。

本机直接内存溢出

直接申请分配内存(实际上并没有真正向操作系统申请分配内存,而是通过计算得知内存无法分配,于是抛出异常。)

public class DirectMemoryOOM {
    private static final int _1MB = 1024 * 1024;
    public static void main(String[] args) throws IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(_1MB);
        }
    }
}