zoukankan      html  css  js  c++  java
  • Java OOM异常

    OOM:Out Of Memory,就是常说的内存溢出。

    出现原因

    堆内存分配过低、代码问题如:死循环、资源未关闭、对象过大或者未及时回收等。

    举例分析

    堆内存分配过低

    解决办法自然就是加大堆空间。

    -Xmx:最大堆大小

    代码问题

    我们写个demo分析下。

    OOMObject.java

    package com.boot.demo.test.jvm;
    
    /**
     * @author braska
     * @date 2020/3/18
     **/
    public class OOMObject {
        public byte[] bytes = new byte[64 * 1024];
    } 

    ListUtil.java

    package com.boot.demo.test.jvm;
    
    import java.util.List;
    
    /**
     * @author braska
     * @date 2020/3/18
     **/
    public class ListUtil {
    
        public static void add(List<OOMObject> list, int num) throws Exception {
            for (int i = 0; i < num; i++) {
                list.add(new OOMObject());
            }
        }
    }

    MapUtil.java

    package com.boot.demo.test.jvm;
    
    import java.util.Map;
    
    /**
     * @author braska
     * @date 2020/3/18
     **/
    public class MapUtil {
    
        public static void put(Map<String, Object> map, int num) {
            for (int i = 0; i < num; i++) {
                map.put(String.format("key%s", i), new OOMObject());
            }
        }
    }

    DumpTest.java

    package com.boot.demo.test.jvm;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * jvm启动参数  -Xmx10M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d://
     *
     * @author braska
     * @date 2020/3/18
     **/
    public class DumpTest {
    
        public static void main(String[] args) throws Exception {
            List<OOMObject> list = new ArrayList<>();
            ListUtil.add(list, 128);
    
            Map<String, Object> map = new HashMap<>();
            MapUtil.add(map, 2);
            while (!Thread.interrupted()) {
                Thread.sleep(100);
            }
        }
    }
    

    控制台输出:

    Connected to the target VM, address: '127.0.0.1:64752', transport: 'socket'
    java.lang.OutOfMemoryError: GC overhead limit exceeded
    Dumping heap to d://java_pid10640.hprof ...
    Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    	at java.util.HashMap.newNode(HashMap.java:1742)
    	at java.util.HashMap.putVal(HashMap.java:641)
    	at java.util.HashMap.put(HashMap.java:611)
    	at java.util.HashSet.add(HashSet.java:219)
    Heap dump file created [10485144 bytes in 0.276 secs]
    	at sun.util.locale.provider.JRELocaleProviderAdapter.createLanguageTagSet(JRELocaleProviderAdapter.java:373)
    	at sun.util.locale.provider.JRELocaleProviderAdapter.getLanguageTagSet(JRELocaleProviderAdapter.java:349)
    	at sun.util.locale.provider.JRELocaleProviderAdapter.getCurrencyNameProvider(JRELocaleProviderAdapter.java:224)
    	at sun.util.locale.provider.JRELocaleProviderAdapter.getLocaleServiceProvider(JRELocaleProviderAdapter.java:100)
    	at sun.util.locale.provider.LocaleServiceProviderPool.<init>(LocaleServiceProviderPool.java:133)
    	at sun.util.locale.provider.LocaleServiceProviderPool.getPool(LocaleServiceProviderPool.java:111)
    	at java.util.Currency.getSymbol(Currency.java:506)
    	at java.text.DecimalFormatSymbols.initialize(DecimalFormatSymbols.java:648)
    	at java.text.DecimalFormatSymbols.<init>(DecimalFormatSymbols.java:113)
    	at sun.util.locale.provider.DecimalFormatSymbolsProviderImpl.getInstance(DecimalFormatSymbolsProviderImpl.java:85)
    	at java.text.DecimalFormatSymbols.getInstance(DecimalFormatSymbols.java:180)
    	at java.util.Formatter.getZero(Formatter.java:2283)
    	at java.util.Formatter.<init>(Formatter.java:1892)
    	at java.util.Formatter.<init>(Formatter.java:1914)
    	at java.lang.String.format(String.java:2940)
    	at com.boot.demo.test.jvm.MapUtil.add(MapUtil.java:13)
    	at com.boot.demo.test.jvm.DumpTest.main(DumpTest.java:21)
    Disconnected from the target VM, address: '127.0.0.1:64752', transport: 'socket'
    
    Process finished with exit code 1
    

    有人可能会疑惑。这么简单一个demo,为什么要拆分这么多各类呢?

    看上面这个会抛出OOM异常的demo,看我们很容易看出产生OOM的主因其实是因为ListUtil的add方法添加了过多的对象,而MapUtil的put操作其实只是压死骆驼的最后一根稻草。但是看控制台的输出,你会发现错误堆栈只能跟踪到MapUtil这边。

    这就是分这么多类的目的。生产环境中,我们的代码量往往是这个demo的成千上百倍。我们又常常根据模块、业务拆分类、包名,使得OOM异常的追踪排查并不会一眼就能看出,日志的输出也不一定准确。

    所以如果你在看完日志后依然找不出OOM原因的时候,你应该学会如何分析程序的dump文件。

    生成dump日志

     启动前

      程序发生OOM时,会自动生成java_pid.hprof文件

    -XX:+HeapDumpOnOutOfMemoryError
    -XX:HeapDumpPath=dump/
    

     运行中

      如果你在应用程序启动前,忘了加jvm参数。没关系jmap命令可以帮助你生成dump快照。

    jmap -dump:format=b,file=dump/ pid
    

      pid的查询方式有很多。linux下,我们可以通过netstat、ps、jps等命令获取到pid。这里我就不细写了。

    分析dump日志

      当我们拿到程序的dump快照时,我们可以使用jdk自带的jhat工具、eclipse的mat工具或者jprofiler工具进行分析。前两者免费,jprofiler收费。

      我们用jprofilter打开hprof文件,选择Biggest Objects。会看到ArrayList对象占用了88%的堆内存。

     从上面我们可以看出,事实上,占用大量堆空间的主谋其实是ArrayList,HashMap只是推手而已。

    可是,ArrayList家户喻晓,使用率那么高,你凭什么说是我写的代码导致的OOM。万一是别人的代码有问题呢?

    这时候,我们可以右键ArrayList对象,选择Use Select Objects。跳转到References页签。Outgoing references表示调用了哪些对象。Incoming references表示谁引用了这个大对象。我们选Incoming References。

    至此,我们就找到了完整的调用链。

  • 相关阅读:
    合并、媒体查询
    混入、命名空间(less)、继承
    函数(内置函数 和 自定义函数)
    运算、单位、转义、颜色
    选择器嵌套、伪类嵌套、属性嵌套(只在Sass中)
    注释、变量、插值、作用域
    二路归并排序java实现
    堆排序Java实现
    和为S的连续正数序列——牛客网(剑指offer)
    transient 与 volatile 笔记
  • 原文地址:https://www.cnblogs.com/braska/p/12518278.html
Copyright © 2011-2022 走看看