常见的六种OOM异常和错误

1、java.lang.StackOverflowError

报这个错误一般是由于方法深层次的调用,默认的线程栈空间大小一般与具体的硬件平台有关。栈内存为线程私有的空间,每个线程都会创建私有的栈内存。栈空间内存设置过大,创建线程数量较多时会出现栈内存溢出StackOverflowError。同时,栈内存也决定方法调用的深度,栈内存过小则会导致方法调用的深度较小,如递归调用的次数较少。

Demo:

public class StackOverFlowErrorDemo {

   static int i = 0;

    public static void main(String[] args) {
        stackOverflowErrorTest();
    }

    private static void stackOverflowErrorTest() {

        i++;
        System.out.println("这是第 "+i+" 次调用");
        stackOverflowErrorTest();

    }
}
//运行结果:
。。。。
这是第 6726 次调用
这是第 6727 次调用
Exception in thread "main" java.lang.StackOverflowError
。。。。

注意:这是一个Error!!!!

2、java.lang.OutOfMemoryError: Java heap space

Heap size 设置 JVM堆的设置是指:java程序执行过程中JVM能够调配使用的内存空间的设置。JVM在启动的时候会自己主动设置Heap size的值,其初始空间(即-Xms)是物理内存的1/64最大空间(-Xmx)是物理内存的1/4。能够利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。Heap size 的大小是Young Generation 和Tenured Generaion 之和。

Demo:

public class OOMHeapSpaceDemo {
    public static void main(String[] args) {
        byte[] bytes = new byte[30*1024*1024];
    }
}

然后修改堆内存的初始容量和最大容量为5MB

运行程序,查看结果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at jvm.OOMHeapSpaceDemo.main(OOMHeapSpaceDemo.java:7)

注意:这是一个Error!!!!

3、java.lang.OutOfMemoryError:GC overhead limit exceeded

GC回收时间过长时会抛出的OutOfMemory。过长是指,超过98%的时间都在用来做GC并且回收了不到2%的堆内存。连续多次的GC,都回收了不到2%的极端情况下才会抛出。假如不抛出GC overhead limit 错误会发生什么事情呢?那就是GC清理出来的一点内存很快又会被再次填满,强迫GC再次执行,这样造成恶性循环,CPU的使用率一直很高,但是GC没有任何的进展。

Demo:

/**
 * 调整虚拟机的参数:
 * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
 */
public class GCOverHeadDemo {
    public static void main(String[] args) {
        int i= 0;
        List<String> list = new ArrayList<>();
        while (true){
            list.add(String.valueOf(++i).intern());
            System.out.println(i);

        }
    }
}


//运行结果:
[Full GC (Ergonomics) [PSYoungGen: 1024K->1024K(2048K)] [ParOldGen: 7101K->7099K(7168K)] 8125K->8123K(9216K), [Metaspace: 3264K->3264K(1056768K)], 0.0296282 secs] [Times: user=0.08 sys=0.00, real=0.03 secs] 

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

可以看到GC前后内存占用时一样的,也就是说虽然在频繁的GC,但是并没有起到什么作用,并没有回收回来多少的内存。

4、java.lang.OutOfMemoryError:Direct buffer memory

写NIO程序经常使用到ByteBuffer来读取或者写入数据,这是一种基于通道与缓冲区的I/O方式。它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中提高性能,因为避免了java堆和Native堆中来回复制数据。

  • ByteBuffer.allocate(capability) :这种方式是分配JVM堆内存,属于GC管辖范围之内。由于需要拷贝,所以速度相对较慢;
  • ByteBuffer.allocateDirect(capability):这种方式是直接分配OS本地内存,不属于GC管辖范围之内,由于不需要内存拷贝所以速度相对较快。

但是如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象就不会被回收。这时候堆内存充足,但是本地内存已经用光了,再次尝试分配的时候就会出现OutOfMemoryError,那么程序就直接崩溃了。

Demo:

/**
 * JVM配置参数:
 * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
 */
public class DirectBufferMemoryDemo {
    public static void main(String[] args) {
        System.out.println("配置的MaxDirectMemorySize"+sun.misc.VM.maxDirectMemory()/(double)1024/1024+" MB");
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6*1024*1024);
    }
}


//运行结果:
配置的MaxDirectMemorySize5.0 MB
[GC (System.gc()) [PSYoungGen: 1785K->488K(2560K)] 1785K->728K(9728K), 0.0019042 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 488K->0K(2560K)] [ParOldGen: 240K->640K(7168K)] 728K->640K(9728K), [Metaspace: 3230K->3230K(1056768K)], 0.0077924 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory

5、java.lang.OutOfMemoryError:unable to create new native thread

准确的说,这一个异常是和程序运行的平台相关的。导致的原因:

  • 创建了太多的线程,一个应用创建多个线程,超过系统承载极限;
  • 服务器不允许应用程序创建这么多的线程,Linux系统默认的允许单个进程可以创建的线程数量是1024个,当创建多 线程数量多于这个数字的时候就会抛出此异常

如何解决呢?

  • 想办法减少应用程序创建的线程的数量,分析应用是否真的需要创建这么多的线程。如果不是,改变代码将线程数量降到最低;
  • 对于有的应用,确实需要创建很多的线程,远超过Linux限制的1024个 限制,那么可以通过修改Linux服务器的配置,扩大Linux的默认限制。

Demo:

public class UnableCreateNewThreadDemo {
    public static void main(String[] args) {
        for (int i = 1; ;i++){
            System.out.println("i = " +i);
            new Thread(()->{
                try {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },i+"").start();
        }
    }
}

//运行结果:
。。。。
i = 92916
i = 92917
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
。。。。

6、java.lang.OutOfMemoryError:MetaSpace

元空间的本质和永久代类似,都是对JVM规范中的方法区的实现。不过元空间与永久代之间最大的区别在于:元空间不在虚拟机中,而是使用的本地内存。因此,默认情况下,元空间的大小仅仅受到本地内存的限制 。

元空间存放了以下的内容:

  • 虚拟机加载的类信息;
  • 常量池;
  • 静态变量;
  • 即时编译后的代码

模拟MetaSpace空间溢出,我们不断生成类往元空间里灌,类占据的空间总是会超过MetaSpace指定的空间大小的

查看元空间的大小:java -XX:+PrintFlagsInitial

Demo:

/**
 * JVM参数配置:
 * -XX:MetaSapceSize=5m
 */
public class MetaSpaceDemo {
    public static void main(String[] args) {
        for (int i = 0; i < 100_000_000; i++) {
            generate("eu.plumbr.demo.Generated" + i);
        }
    }
    public static Class generate(String name) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        return pool.makeClass(name).toClass();
    }
}


posted @ 2020-01-21 15:31  Simon-Lau  阅读(4423)  评论(0编辑  收藏  举报