• OutOfMemoryError异常java内存泄漏(Memory Leak)和内存溢出(Memory Overflow)


    本篇文章理解源自于《深入理解java虚拟机》2.4章节 实战:OutOfMemoryError异常
     
    在以下例子中,所有代码都可以抛出OutOfMemoryError异常,但是要区分到底是内存泄漏(Memory Leak)还是内存溢出(Memory Overflow),我们需要借助Eclipse Memory Analyzer(也成为MAT,mat是一个分析Java内存的神器)插件来分析.hprof文件才能得知。
    安装Eclipse Memory Analyzer插件方法:eclipse -> Help -> Eclipse Marketplace -> search框搜索“Memory Analyzer” -> 点击install即可安装。
    IBM官方介绍Eclipse Memory Analyzer网址:https://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html?ca=drs-
     
    在测试以下例子之前,我们来了解下VM参数是个什么东西:
    VM arguments参数解析
    -verbose:gc 参数表示将在控制台输出full GC详细,例如:[Full GC 168K->97K(1984K), 0.0253873 secs]
    -Xms20M 初始堆大小。默认值:物理内存的1/64(<1GB)。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制。
    -Xmx20M 最大堆大小。默认值:物理内存的1/4(<1GB)。默认空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。
    -Xmn10M 年轻代大小。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
    -XX:+PrintGCDetails 打印full gc的详细信息。
    -XX:+HeapDumpOnOutOfMemoryError JVM 就会在发生内存泄露时抓拍下当时的内存状态,也就是我们想要的堆转储文件。也就是会在项目根目录下生成*.hprof文件可供分析。
    -XX:+HeapDumpOnCtrlBreak 如果你不想等到发生崩溃性的错误时才获得堆转储文件,也可以通过设置如下 JVM 参数来按需获取堆转储文件。
    -XX:SurvivorRatio=8 Eden区与Survivor区的大小比值。设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10。
    VM参数参考该文章:JVM系列三:JVM参数设置、分析(http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
     
    new一个Junit4单元测试类VirtualTest,依次放入以下例子代码:
    例子1
    /**
    * 以Run AS -> Run Configurations运行。
    * 并设置 VM arguments参数如下:
    * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError
    */
    @Test
    public void testOutOfMemoryError() {
      List<VirtualTest> list = new ArrayList<VirtualTest>();
      while(true){
        list.add(new VirtualTest());
      }
    }
    执行代码后,控制台抛出异常如下:
    [GC (Allocation Failure) [PSYoungGen: 8192K->1018K(9216K)] 8192K->2211K(19456K), 0.0078156 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
    [GC (Allocation Failure) [PSYoungGen: 9210K->1000K(9216K)] 10403K->7720K(19456K), 0.0356040 secs] [Times: user=0.11 sys=0.00, real=0.04 secs]
    …………
    java.lang.OutOfMemoryError: Java heap space
    Dumping heap to java_pid10604.hprof ...
    Heap dump file created [29234562 bytes in 0.214 secs]
    Heap
    PSYoungGen total 9216K, used 8154K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
    eden space 8192K, 99% used [0x00000000ff600000,0x00000000ffdf6a10,0x00000000ffe00000)
    from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
    to space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
    ParOldGen total 10240K, used 9292K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
    object space 10240K, 90% used [0x00000000fec00000,0x00000000ff513390,0x00000000ff600000)
    Metaspace used 4979K, capacity 5182K, committed 5248K, reserved 1056768K
    class space used 591K, capacity 626K, committed 640K, reserved 1048576K
    异常分析
    这个异常我们要借助Memory Analyzer工具来查看内存溢出的是哪个对象,刷新整个项目,点击根目录下的java_pid10604.hprof,默认是Overview视图,上面是一个显眼的饼图:
     
    在这个饼图里面,我们可以看到有一个可疑对象在总共16.4MB的内存里,它占用了15.5MB的内存,占用了94.5%的内存。
    在饼图的左下方,这个报告告诉我们这个内存溢出发生在main这个线程里面。
    接下来我们当然想知道到底是哪个对象干的,那么找到饼图的下方还有一些工具:
    点击Histogram链接,打开Histogram视图:
    我们发现在Objets这一栏,最多的一个对象实例竟然达到810327个,这个对象类是com.virtual.VirtualTest。那么很清楚了,我们new出了太多这个类对象,导致垃圾内存溢出。
    在dominator_tree视图中,可以看到main线程占用了94.02%的内存
    在上图中,我们可以看到该.hprof文件存放的路径、大小、生成时间、格式、报告中总共产生多少对象,堆大小(16.4MB)等信息。
     
    例子2
      /**
         * 以Run AS -> Run Configurations运行。
         * 并设置 VM arguments参数如下:
         * -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
         * 注意:-XX:+HeapDumpOnOutOfMemoryError参数将会使项目根目录下产生java_*.hprof文件
         */
        @Test
        public void testOutOfMemory2(){
            List<String> list = new ArrayList<String>();
            for(int i=0;i<10000000;i++){
                String str = new String();
                list.add(str);
            }
        }
    执行代码后,控制台抛出异常如下
    java.lang.OutOfMemoryError: GC overhead limit exceeded
    Dumping heap to java_pid3360.hprof ...
    Heap dump file created [12525143 bytes in 0.136 secs]
    异常分析
    Sun 官方对此的定义是:“并行/并发回收器在GC回收时间过长时会抛出OutOfMemroyError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存。用来避免内存过小造成应用不能正常工作。“
    意思就是GC用尽了全力可还是来不及回收你new出来的那么多string对象,而且list对象越来越大,你还往里面塞对象,还让它活着,你还不让我回收,于是……呵呵……我挂了(>﹏<)
    但是我抛出这个异常的目的就是在JVM彻底死了之前告诉你我挂了,好让你来得及实施一些紧急措施,比如紧急保存之类。
    参考文章《java.lang.OutOfMemoryError:GC overhead limit exceeded填坑心得》http://www.cnblogs.com/hucn/p/3572384.html
     
    总结
    到目前为止,我还没能从Eclipse Memory Analyzer工具中发现能够区分出java内存泄漏(Memory Leak)和内存溢出(Memory Overflow)这两种状态的直接证据。
    个人理解为:
    java内存泄漏(Memory Leak) 当new出很多对象,而GC无法及时回收这些对象时,会导致内存泄漏。
    内存溢出(Memory Overflow) 当一个对象长时间存活,且占用内存巨大直接威胁到总内存时,会导致内存溢出。
     
    以下是其它已在JDK1.8版本中不再报内存溢出的代码:
    举例1
      /**
         * 在JDK1.8中不会抛出异常!!!! 
         * 以Run AS -> Run Configurations运行。 并设置 VM arguments参数如下:
         * -Xss128k -XX:+HeapDumpOnOutOfMemoryError
         * 注意:-XX:+HeapDumpOnOutOfMemoryError参数将会使项目根目录下产生java_*.hprof文件
         */
        @Test
        public void testOutOfMemory_noError() {
            int stackLength = 1;
            try {
                while (true) {
                    stackLength = stackLength + 1;
                    System.out.println("stack length ==" + stackLength);
                }
            } catch (Throwable t) {
                System.out.println("stack length : " + stackLength);
                throw t;
            }
        }
    举例2
      /**
         * 在JDK1.8中不会抛出异常!!!! 但是会使操作系统假死。
         * 以Run AS -> Run Configurations运行。 并设置 VM arguments参数如下:
         * -Xss2M -XX:+HeapDumpOnOutOfMemoryError
         * 注意:-XX:+HeapDumpOnOutOfMemoryError参数将会使项目根目录下产生java_*.hprof文件
         */
        @Test
        public void testOutOfMemory_noError2() {
            while (true) {
                Thread t = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while (true) {
                        }
                    }
                });
                t.start();
            }
        }
    举例3
      /**
         * 在JDK1.8中不会抛出异常!!!! 
         * 以Run AS -> Run Configurations运行。 并设置 VM arguments参数如下:
         * -XX:PermSize=10M -XX:MaxPermSize=10M -XX:+HeapDumpOnOutOfMemoryError
         * 注意:-XX:+HeapDumpOnOutOfMemoryError参数将会使项目根目录下产生java_*.hprof文件
         */
        @Test
        public void testOutOfMemory_noError3() {
            List<String> list = new ArrayList<String>();
            int i=0;
            while (true) {
                list.add(String.valueOf(i));
            }
        }
    举例4
      /** 
         * 在JDK1.8中不会抛出异常!!!! 
         * 以Run AS -> Run Configurations运行。 并设置 VM arguments参数如下: -Xms10m -Xmx10m
         * -XX:PermSize=10M -XX:MaxPermSize=10M -XX:+HeapDumpOnOutOfMemoryError
         * 注意:-XX:+HeapDumpOnOutOfMemoryError参数将会使项目根目录下产生java_*.hprof文件
         */
        @Test
        public void testOutOfMemory_noError4() {
            while(true){
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(this.getClass());
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    
                    @Override
                    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
                        return proxy.invoke(obj, args);
                    }
                });
                enhancer.create();
            }
        }
     
     
     
     
    支付宝扫一扫,为女程序员打赏!
    作者:梦幻朵颜
    版权:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    移植spdylay到libcurl
    用到的C++标准库
    libcurl底层调用逻辑
    socket编程
    linux的一些机制Signal, Fork,
    openssl 编程
    对称加密,非对称加密
    ajax提交整个form表单
    一道基础的for语句js编译过程
    怎样将浏览器一句话变为文本编辑器
  • 原文地址:https://www.cnblogs.com/zhuwenjoyce/p/6536846.html
Copyright © 2020-2023  润新知