• 常见的六种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();
        }
    }
    


  • 相关阅读:
    linux系统调用之系统控制
    linux系统调用之文件系统操作
    使用EF框架实现MVC的增删改查功能
    MVC+EF快速弄出一个CRUD
    Entity Framework 全面教程详解(转)
    微信小程序学习
    为Bootstrap模态对话框添加拖拽移动功能
    Razor语法大全
    EXCEL怎么打20位以上的数字?
    C# SQLite 数据库操作学习
  • 原文地址:https://www.cnblogs.com/simon-1024/p/12221917.html
Copyright © 2020-2023  润新知