• OutOfMemoryError异常


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

    Java堆溢出

    设置Idea堆的大小为20MB,不可扩展(-Xms参数与最大值-Xmx参数设置为一样,避免自动扩展)

    -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8

    运行以下代码:

    package memory;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class HeepOOM {
        static class OOMObject{
    
        }
    
        public  static  void  main(String[] args){
            List<OOMObject> list = new ArrayList<>();
            while (true){
                list.add(new OOMObject());
            }
        }
    }
    抛出错误:
    
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at java.base/java.util.Arrays.copyOf(Arrays.java:3512)
        at java.base/java.util.Arrays.copyOf(Arrays.java:3481)
        at java.base/java.util.ArrayList.grow(ArrayList.java:237)
        at java.base/java.util.ArrayList.grow(ArrayList.java:244)
        at java.base/java.util.ArrayList.add(ArrayList.java:454)
        at java.base/java.util.ArrayList.add(ArrayList.java:467)
        at memory.HeepOOM.main(HeepOOM.java:14)

    解决这个异常重点是确认内存中的对象是否是必要的,也就是区分是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)

    • 内存泄漏:程序申请内存后,无法释放已申请的内存空间,多次内存泄漏堆积后的后果就是内存溢出
    • 内存溢出:程序申请内存时,没有足够的内存供申请者使用

    如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Root相关联导致垃圾回收器无法自动回收他们。

    如果不存在泄漏,则就应该检查虚拟机堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少长期运行期的内存消耗。

    虚拟机栈和本地方法栈溢出
    • 如果线程请求的栈深度大于虚拟机所允许的最大深度,则抛出StackOverflowError异常。
    • 如果虚拟机在扩展栈时无法申请到足够的内存,则抛出OutOfMemoryError异常。
    package memory;
    
    public class JavaVmStackSOF {
        private int stackLength = 1;
    
        public  void  stackLeak(){
            stackLength++;
            stackLeak();
        }
                /**
         * -Xss180K -设置每个线程分配180K内存
         * @param args
         * @throws Throwable
         */
        public static void main(String[] args) throws Throwable{
            JavaVmStackSOF oom = new JavaVmStackSOF();
            try{
                oom.stackLeak();
            }catch (Throwable e){
                System.out.println("stack length:"+oom.stackLength);
                throw  e;
            }
        }
    }

    运行结果:

    Exception in thread "main" java.lang.StackOverflowError
    stack length:1554
        at memory.JavaVmStackSOF.stackLeak(JavaVmStackSOF.java:7)
    package memory;
    
    public class JavaVMStackOOM {
    
       private void dontStop(){
           while (true){
    
           }
       }
    
       public  void  stackLeakByThread(){
           while (true){
               Thread thread = new Thread(new Runnable() {
                   @Override
                   public void run() {
                       dontStop();
                   }
               });
               thread.start();
           }
       }
    
       /**
        * -Xss2M -设置每个线程分配2M内存
        * 最终会耗尽所有内存,导致没有足够的内存创建线程而抛出异常:OutOfMemoryError
        * @param args
        * @throws Throwable
        */
       public static void main(String[] args) throws Throwable{
           JavaVMStackOOM oom = new JavaVMStackOOM();
           oom.stackLeakByThread();
       }
    }

    运行结果

    Exceptuib in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

    运行时常量池溢出
    运行时常量池属于方法区,因此可以通过以下方式模拟:
    java7可以通过设置:-XX:PermSize=10M -XX:MaxPermSize=10M 来限定方法区。
    java8之后永久代被移除,-XX:PermSize、-XX:MaxPermSize已经被移除;可以使用:-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M (元空间代替)
    类型信息、字段、方法、常量保存在元空间。

    package main.java.loadclass;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class RuntimeConstantPoolOOM {
    
                /**
         * JAVA7 下运行
         * @param args
         */
        public static void main(String[] args){
            //使用LIST保持着常量池引用,避免Full GC 回收常量池行为
            List<String> list = new ArrayList<>();
            //10MB的PermSize在integer范围内足够产生OOM了
            int i = 0;
            while (true){
                list.add(String.valueOf(i++).intern());
            }
        }
    }

    抛出以下异常:java.lang.OutOfMemoryError:PermGen space

    方法区溢出
    方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。一个类如果被垃圾回收器回收掉,判定条件非常苛刻,在经常动态生成大量Class的应用中,需要特别注意类的回收状态。

    本机直接内存溢出
    DirectMemory容量可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆的最大值(-Xmx指定)一样。
    DirectByteBuffer是Java用于实现堆外内存的一个重要类,我们可以通过该类实现堆外内存的创建、使用和销毁。DirectByteBuffer跑出内存溢出异常时并没有真正整整向操作系统申请分配内存,而是通过计算得知内存无法分配,手动跑出异常。
    使用Unsafe类的allocateMemory方法是真正分配内存。

    package main.java.loadclass;
    
    import sun.misc.Unsafe;
    
    import java.lang.reflect.Field;
    
    public class DirectMemoryOOM {
    
        private static final int _1MB = 1024 * 1024;
    
        /**
         * -Xmx20M -XX:MaxDirectMemorySize=10M
         * @param args
         * @throws Exception
         */
        public  static  void main(String[] args) throws Exception{
            Field unsefeField = Unsafe.class.getDeclaredFields()[0];
            unsefeField.setAccessible(true);
            Unsafe unsafe = (Unsafe) unsefeField.get(null);
            while (true){
                unsafe.allocateMemory(_1MB);
            }
        }
    }

    返回结果:

    Exception in thread "main" java.lang.OutOfMemoryError
        at java.base/jdk.internal.misc.Unsafe.allocateMemory(Unsafe.java:616)
        at jdk.unsupported/sun.misc.Unsafe.allocateMemory(Unsafe.java:462)
        at main.java.loadclass.DirectMemoryOOM.main(DirectMemoryOOM.java:16)

  • 相关阅读:
    解决winXP无法远程桌面连到win8
    Exception处理
    Java父类与子类的内存引用讲解
    JAVA子类继承父类
    JAVA子类调用父类构造方法
    JS 矩阵转置
    JS 二分查找
    JS冒泡排序
    JS 求平均值
    关于STM32 NVIC配置的解释
  • 原文地址:https://www.cnblogs.com/xtt321/p/16218167.html
Copyright © 2020-2023  润新知