文档章节

OutOfMemoryError详解

六只
 六只
发布于 2012/07/04 23:47
字数 2329
阅读 1639
收藏 5

絮絮叨叨

最近在看周志明的《深入理解Java虚拟机》,虽然刚刚开始看,但是觉得还是一本不错的书。对于和我一样对于JVM了解不深,有志进一步了解的人算是一本不错的书。注明:不是书托,同样是华章出的书,质量要比《深入剖析Tomcat》高好多,起码排版上没有那么多严重的失误,停,等哪天心情不好再喷那本书。:)(还有一本书让我看完觉得挺不爽的,当然不排除自身问题)

刚刚看了两章,第一章我比较关注如何自己编译openJdk,额,现在还没捣腾成功,完成后再分享,暂且跳过;本篇文章的主要任务是记录书中关于产生OutOfMemoryError异常的原因。代码以及说明基本都是出自原书,写这篇文章意在加深印象,同时分享给那些没有读过这本书的人。说句自己的一次经历,不记得是在哪家公司面试来着,面试官曾经问过我都有哪些情况会造成OutOfMemoryError异常。很遗憾,当时我不会。

设置运行时参数

说下为什么加了这样一节,说来惭愧,第一次设置运行时参数,找不到在哪里设置,找了半天才找对位置,怕有和我一样小白的人存在,所以就增加了这样一个小节。(IDE工具是eclipse)

按照如下三步设置即可,呈现一场代码的注释中会标注每种情形需要设置的运行时参数。

step1:
step2:

step3:


可以这样为每个含有main函数的类指定自己的运行时参数。

造成内存溢出之五大元凶

个人觉得程序员都要有”刨祖坟”的精神,文艺一点儿就是知其然,知其所以然。在日常的工作中更应该如此,不能说要实现一个功能就满口答应,起码要知道为什么需要这样一个功能,解决什么问题,是否合理。如果连原因都不知道,真心不相信能把这个功能做好。也许这个也是好管理和不好管理程序员的分割线。如果说发生OutOfMemoryError跟我们无关,那我们为什么要知道发生的原因啊,美国打伊拉克我和程序员有毛关系啊。其实这个异常对大家来说应该都不陌生,之前我最爱的处理就是从新再运行一次,不行关闭eclipse,再不行重启电脑。(杀手锏级别的解决方案).可是这样不科学,科学的方式就要求我们知道为什么会发生这个异常,换句话说是发生这个异常的场景,然后通过打印出的异常信息快速定位发生内存溢出的区域,然后进行权衡,调整运行时参数来解决。

Java堆溢出

Java堆用于存储对象实例,知道这一点就很容易呈现堆溢出,不断的创建对象,并且保持有指向其的引用,防止为gc。

代码如下:

import java.util.ArrayList;
  import java.util.List;

  /**
   * VM Args:-Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
   *
   */
  public class HeapOOM {
    
    static class OOMObject{
      
    }
    
    public static void main(String[] args) {
      List<OOMObject> list = new ArrayList<OOMObject>();
      
      while(true){
        list.add(new OOMObject());
      }
    }

  }

通过设置-Xms20M -Xmx20M都为20M意在防止堆大小自动扩展,更好的展现溢出。 执行结果如下:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

是不是很明显啊,显示堆空间发生OutOfMemoryError。

书中告诉我们发生了OutOfMemoryError后,通常是通过内存影像分析工具对dump出来的堆转储快照进行分析(这就是运行时参数中配置-XX:+HeapDumpOnOutOfMemoryError的原因),重点是确定是由内存泄露(Memory Leak)还是有内存溢出(Memory Overflow)引起的OutOfMemoryError。如果是内存泄露则找到泄露点,修正;如果确实是合理的存在,那么就增加堆空间。(内存分析这里我也木有做过,工具也木有使用过,在后续章节会有介绍,用熟了后再来一篇)

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

由于在HotSpot虚拟机中并不区分虚拟机栈和本地方法区栈,因此对于HotSpot来说,-Xoss(设置本地方法栈大小)参数是无效的,栈容量由-Xss参数设定。关于虚拟机栈和本地方法区栈,在Java虚拟机规范中描述了两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常
  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
  • 书中谈到单线程的场景下只能浮现StackOverflowError,那我们就先来看看单线程场景下到底会是什么样子。

    /**
     * 
     * VM Args:-Xss128k
     */
    public class JavaVMStackSOF {
      private int stackLength = 1;
      private void stackLeak() {
        stackLength++;
        stackLeak();
      }
      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;
        }
      }
    }

    通过-Xss128k设置虚拟机栈大小为128k,执行结果如下:

    执行结果显示,确实是发生了StackOverflowError异常。

    通过不断创建线程耗尽内存也可以呈现出OutOfMemoryError异常,但是在Windows平台下模拟会使系统死机,我这里就不多说了。感兴趣的可以自己去尝试。

    运行时常量池溢出

    向运行时常量池中添加内容最简单的方式就是使用String.intern()方法。由于常量池分配在方法区内,可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。

    代码如下:

    import java.util.ArrayList;
      import java.util.List;
    
      /**
       * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
       * 
       */
      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());
          }
        }
    
      }

    这里有个小小的插曲,之前有听说在jdk7中将永久区(方法区和常量池)给干掉了,没有验证过。永久区可以说是在堆之上的一个逻辑分区。如果jdk7中去掉了,那么这个示例应该会抛出堆空间的内存溢出,而非运行时常量池的内存溢出。所以在执行程序的时候分别用了jdk6和jdk7两个版本。多说一句,如果jdk7去掉了方法区,那么-XX:PermSize=10M -XX:MaxPermSize=10M就不起作用了,所以在jdk7环境下运行时,堆大小为jvm默认的大小,要执行一会儿(半小时左右:( ))才能抛出异常。没关系,再配置下运行时参数即可,注意要配置成不可扩展。以图为据:

  • jdk6环境下抛出运行时常量池内存溢出
  • Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

    显而易见PermGen space,永久区。不解释。

  • jdk7环境下,运行时参数为:-XX:PermSize=10M -XX:MaxPermSize=10M

  • Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

    运行了好久好久,最终抛出堆内存溢出。Java heap space已经足够说明问题了。

  • jdk7环境下,运行时参数为:-verbose:gc -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError

  • Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

    同样也是堆内存溢出,不过速度就快了好多好多,因为堆大小被设置为不可扩展。

    方法区溢出

    方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。测试这个区域只要在运行时产生大量的类填满方法区,知道溢出。书中借助CGlib直接操作字节码运行时,生成了大量的动态类。

    当前主流的Spring和Hibernate对类进行增强时,都会使用到CGLib这类字节码技术,增强的类越多,就需要越大的方法区来保证动态生成的Class可以加载到内存。

    测试代码如下:

    import net.sf.cglib.proxy.Enhancer;
      import net.sf.cglib.proxy.MethodInterceptor;
      import net.sf.cglib.proxy.MethodProxy;
    
      /**
       * VM Args:-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.setCallback(new MethodInterceptor() {
              public Object intercept(Object object, Method method,
                  Object[] args, MethodProxy proxy) throws Throwable{
                return proxy.invokeSuper(object, args);
              }
            });
            enhancer.create();
          }
    
        }
    
        static class OOMObject {
    
        }
    
      }

    工程中要引入cglib-2.2.2.jar和asm-all-3.3.jar。

    方法区的内存溢出问题同样存在jdk6和jdk7版本之间的区别,同运行时常量池内存溢出。

    方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收掉,判定条件是非常苛刻的。在经常动态生成大量Class的应用中,需要特别注意类的回收状况。这类场景除了上面提到的程序使用了CGLib字节码增强外,常见的还有:大量JSP或动态生成JSP文件的应用、基于OSGi的应用等。

    本机直接内存溢出

    DirectMemory容量可以通过-XX:MaxDirectMemorySize指定。

    示例代码如下:

    import java.lang.reflect.Field;
      import sun.misc.Unsafe;
    
      /**
       * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
       *
       */
      public class DirectMemoryOOM {
    
        private static final int _1MB = 1024 * 1024;
    
        /**
         * @param args
         * @throws IllegalAccessException
         * @throws IllegalArgumentException
         */
        public static void main(String[] args) throws IllegalArgumentException,
            IllegalAccessException {
          // TODO Auto-generated method stub
          Field unsafeField = Unsafe.class.getDeclaredFields()[0];
          unsafeField.setAccessible(true);
          Unsafe unsafe = (Unsafe) unsafeField.get(null);
          
          while(true){
            unsafe.allocateMemory(_1MB);
          }
        }
    
      }

    运行结果如下图:

    抛出内存溢出异常。不解释。

     本文出自:http://www.congmo.net/blog/2012/07/01/java-outofmemory/



    © 著作权归作者所有

    六只

    六只

    粉丝 12
    博文 2
    码字总数 3556
    作品 0
    海淀
    程序员
    私信 提问
    加载中

    评论(3)

    可以参考下资料网: http://www.baisoujs.com/
    要做攻城师的志
    要做攻城师的志
    嗯嗯,这本书我几个月前也看过,讲得很有深度,我看了五遍,还是有很多不懂的地方,现在很多东西又忘记了,这本书对我来说还是很有难度的
    邢波涛
    邢波涛
    分析的不错,赞一个
    Java深入 - Java内存区域详解

    Java和c c++不一样,c c++都是直接通过手动的方式来申请内存,释放内容。而java拥有良好的内存自动管理机制。所以在我们开发java程序代码的时候,一般情况下不需要关心java的内存问题。 Java...

    闪电
    2016/07/04
    20
    0
    Java内存区域详解

    对于C/C++开发人员,在内存管理方面,他们拥有绝对权力,既认拥有每一个对象的所有权,又担负着每一个对象生命开始到结束的维护责任。 而对于java开发人员,在JVM自动内存管理机制帮助下,不...

    武祖林动
    2017/06/13
    446
    4
    Java内存管理原理及内存区域详解

    一、概述 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干不同的数据区域,这些区域都有各自的用途以及创建和销毁的时间。Java虚拟机所管理的内存将会包括以下几个运行时数据...

    亚特兰缇斯
    2015/09/04
    43
    0
    Java基础之内存管理原理及内存区域详解

    一、概述 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干不同的数据区域,这些区域都有各自的用途以及创建和销毁的时间。Java虚拟机所管理的内存将会包括以下几个运行时数据...

    白志华
    2015/09/23
    76
    0
    JVM(二)Java虚拟机组成详解

    导读:详细而深入的总结,是对知识“豁然开朗”之后的“刻骨铭心”,想忘记都难。 Java虚拟机(Java Virtual Machine)下文简称jvm,上一篇我们对jvm有了大体的认识,进入本文之后我们将具体...

    王磊的博客
    01/14
    153
    0

    没有更多内容

    加载失败,请刷新页面

    加载更多

    如何在Linux中复制文档

    在办公室里复印文档过去需要专门的员工与机器。如今,复制是电脑用户无需多加思考的任务。在电脑里复制数据是如此微不足道的事,以致于你还没有意识到复制就发生了,例如当拖动文档到外部硬盘...

    老孟的Linux私房菜
    51分钟前
    19
    0
    SpringBoot 集成MongoDB

    一、MongoDB 简介 MongoDB 如今是最流行的 NoSQL 数据库,被广泛应用于各行各业中,很多创业公司数据库选型就直接使用了 MongoDB,但对于大部分公司,使用 MongoDB 的场景是做大规模数据查询...

    zw965
    今天
    28
    0
    使用 Envoy 和 AdGuard Home 阻挡烦人的广告

    > 原文链接:使用 Envoy 和 AdGuard Home 阻挡烦人的广告 通常我们使用网络时,宽带运营商会为我们分配一个 DNS 服务器。这个 DNS 通常是最快的,距离最近的服务器,但会有很多问题,比如: ...

    米开朗基杨
    今天
    35
    0
    springboot之全局处理异常封装

    springboot之全局处理异常封装 简介 在项目中经常出现系统异常的情况,比如NullPointerException等等。如果默认未处理的情况下,springboot会响应默认的错误提示,这样对用户体验不是友好,系...

    Purgeyao
    今天
    42
    0
    cookie

    cookie: n. 饼干;小甜点 为什么会引入Cookie(在客户端保持http状态) 因为http协议是一种无状态协议,web服务器本身不能识别出哪些请求是同一个服务器发送的,浏览器的每一次请求都是独立...

    五公里
    今天
    41
    0

    没有更多内容

    加载失败,请刷新页面

    加载更多

    返回顶部
    顶部