zoukankan      html  css  js  c++  java
  • 了解OutOfMemoryError异常

    JVM中的异常发生

      Java虚拟机规范中除了程序计数器外,其他几个运行时区域都有发生OutOfMemoryError异常的可能。
      本章笔记通过代码来验证Java虚拟机规范中描述的各个运行时区域存储的内容、以及在以后遇到实际的内存溢出异常时,能根据异常的信息快速判断是哪个区域出现的内存溢出、怎样的代码可能会导致这些区域的内存溢出、以及这些问题该如何处理。
    1. Java堆溢出:Java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径避免垃圾回收机制清除对象,就会在对象数量到达最大堆的容量限制后产生内存溢出异常。-Xms设置堆最小值、-Xmx设置堆最大值、-XX:+HeapDumpOnOutOfMemoryError设置当虚拟机出现OOM异常时,dump出当前内存堆转存快照。其中当把堆最小值与最大值设置相同时候,堆为不可扩展。
      /**
       * -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
       */
      public class HeapOOM {
           static class OOMObject {
                 Object obj = new Object();
           }
           public static void main(String[] args) {
                 List<OOMObject> list = new ArrayList<OOMObject>();
                  while( true) {
                      list.add( new OOMObject());
                 }
           }
      }
      要解决这个区域的异常,可以通过内存映像分析工具堆dump出来的快照进行分析,重点是确认内存是出现了泄漏(Memory Leak)还是内存溢出(Memory Overflow)。如果是内存泄漏通过工具查看相关引用链。如果是内存溢出,应当检查虚拟机的堆参数,与机器物理内存对比确认是否可以调大,代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况。
    2. 虚拟机栈和本地方法栈溢出:Java虚拟机中关于虚拟机栈和本地方法栈描述了两种异常
      StackOverflowError:线程请求的栈深度大于虚拟机所允许的最大深度时抛出
      OutOfMemoryError:虚拟机在扩展栈时无法申请到足够的内存空间时抛出
      -Xoss参数设置本地方法栈内存容量,-Xss参数设置虚拟机栈内存容量。
      /**
       * -Xss128k
       * 使用-Xss减小栈内存容量.
       * 定义大量本地变量,增加方法帧中本地变量表长度.
       */
      public class JavaVMStackSOF {
           private int stackLength = 1;
           public static void main(String[] args) {
                 JavaVMStackSOF javaVMStackSOF = new JavaVMStackSOF();
                  try {
                      javaVMStackSOF.test();
                 } catch(java.lang.StackOverflowError e) {
                      System. out.println(javaVMStackSOF. stackLength);
                 }
           }
           public void test() {
                 ++ stackLength;
                 test();
           }
      }
      单线程下,无论是由于栈帧太大,还是虚拟机栈容量太小,当内存无法分配时,虚拟机抛出的都是StackOverflowError异常。如果通过建立线程的方式可以产生内存溢出异常,但这样产生的异常与栈空间是否足够大并没有任何的关系,因为这种情况下,给线程的栈分配的内存越大,越容易产生内存溢出异常。原因:栈的生命周期与线程相同,每个线程分配到的栈容量越大,可以创建的线程数量就会越少。另外,虚拟机中剩余内存计算方法计算如下:剩余内存 = 操作系统限制进程内存 - 最大堆容量 - 最大方法区容量。程序计数器消耗内存很小,可忽略、若虚拟机进程本身不计算的话,剩下的就是虚拟机栈本地方法栈分了。因此如果是建立过多线程导致的内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减少最大堆(我认为应该也可以减少最大方法区)和减少栈容量来换取更多的线程了。
    3. 运行时常量池溢出:运行时常量分配在方法区内,可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制常量池的容量。实验代码在jdk 1.7.0_72上实验失败。
      /**
       * -XX:PermSize=10M -XX:MaxPermSize=10M
       * @author Administrator
       *
       */
      public class RuntimeConstantPoolOOM {
           public static void main(String[] args) {
                 List<String> list = new ArrayList<String>();
                  int i = 0;
                  while( true) {
                      list.add(String. valueOf(i++).intern());
                 }
           }
      }
      运行时常量池溢出,在OutOfMemoryError后面跟随的提示信息是"PermGen space",说明运行时常量池属于方法区的一部分
    4. 方法区溢出:方法区溢出是一种常见的内存溢出异常,可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,在经常动态生成大量Class的应用中,需要特别注意类的回收状况。动态生成Class可以使用CGLib字节码增强、动态JSP文件、基于OSGi的应用。下面的代码就是借助CGLib使方法区出现内存溢出异常
      /**
       * -XX:PermSize=10M -XX:MaxPermSize=10M
       */
      public class JavaMethodAreaOOM {
           public static void main(String[] args) {
                  while( true) {
                      Enhancer enhancer = new Enhancer();
                      enhancer.setSuperclass(OOMObject. class);
                      enhancer.setUseCache( false);
                      enhancer.create();
                 }
           }
           static class OOMObject {
           }
      }
    5. 本机直接内存溢出:直接内存(DirectMemory)容量可以通过-XX:MaxDirectMemorySize指定,如果不指定MaxDirectMemorySize默认值与Java堆的最大值一样。下面的代码使用Unsafe功能申请分配内存。
      /**
       * -Xmx20M -XX:MaxDirectMemorySize=10M
       */
      public class DirectMemoryOOM {
           private static final int _1MB = 1024*1024;
           public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
                 Field unsafeField = sun.misc.Unsafe.class.getDeclaredFields()[0];
                 unsafeField.setAccessible( true);
                 sun.misc.Unsafe unsafe = (sun.misc.Unsafe)unsafeField.get( null);
                  while( true) {
                      unsafe.allocateMemory( _1MB);
                 }
           }
      }

    方法入参建议使用JavaBean传递

      当方法参数很多时候,不便于阅读和维护,因此建议使用一个JavaBean来包装入参。这个建议的另外一方面因素请先考虑下面这个问题

    public class MyBean {
        private final double a;
        private final double b;
    
        public MyBean(double a, double b) {
            this.a = a;
            this.b = b;
        }
    
        public double getA() {
            return a;
        }
    
        public double getB() {
            return b;
        }
    }
    public class StackOverflowTestA {
    
        private static int invokeMethodCount = 0;
    
        public static void method(MyBean myBean) {
            ++invokeMethodCount;
            System.out.println(invokeMethodCount);
            //method(new MyBean(myBean.getA(), myBean.getB()));
            method(myBean);
        }
    
        public static void main(String[] args) {
            try {
                method(new MyBean(1L, 2L));
            } catch(Throwable e) {
                System.out.println("调用次数 : " + invokeMethodCount);
            }
        }
    
    }
    public class StackOverflowTestB {
    
        private static int invokeMethodCount = 0;
    
        public static void method(long a, long b) {
            ++invokeMethodCount;
            System.out.println(invokeMethodCount);
            method(a, b);
        }
    
        public static void main(String[] args) {
            try {
                method(1L, 2L);
            } catch(Throwable e) {
                System.out.println("调用次数 : " + invokeMethodCount);
            }
        }
    }

      我们都知道Java虚拟机栈的空间有限,当出现无限递归的代码时就会发生StackOverflowError错误。
      那么请思考上面代码中StackOverflowTestA和StackOverflowTestB哪一个递归的深度更深一些?如果StackOverflowTestB中method方法入参变为两个short类型呢?

  • 相关阅读:
    人生
    问模板函数、函数模板,模板类、类模板的区别的问题?
    李清照
    重师者王,重友者霸,重己者亡
    C++ Primer(第4版)习题解答
    C#设计模式(1)转载
    C#设计模式(5)
    C#设计模式(2)
    C# 编码规范和编程好习惯
    C#设计模式(4)
  • 原文地址:https://www.cnblogs.com/sealedbook/p/6285999.html
Copyright © 2011-2022 走看看