美文网首页java性能测试软件测试
作为测试你应该知道的JAVA OOM及定位分析

作为测试你应该知道的JAVA OOM及定位分析

作者: 灼灼2015 | 来源:发表于2016-07-18 20:12 被阅读1000次

    上周现网一个内存溢出问题导致应用服务器每隔一小时死一次,遂整理下常见的OMM、发现方法和处理方式,加入Bug预防。

    常见的OutOfMemoryError有三种:OutOfMemoryError:PermGen space、OutOfMemoryError:Java heap space、OutOfMemoryError:unable to create new native thread

    1. OutOfMemoryError:PermGen space
    原因:程序中使用了大量的jar或class,使java虚拟机装载类的空间不够,与Permanent Generation space有关。
    处理:
    一:增加java虚拟机中的XX:PermSize和XX:MaxPermSize参数的大小,其中XX:PermSize是初始永久保存区域大小,XX:MaxPermSize是最大永久保存区域大小。

    JAVA_OPTS="-XX:PermSize=64M -XX:MaxPermSize=128m"
    

    二: 清理应用程序中WEB-INF/lib下的jar,用不上的jar删除掉,多个应用公共的jar移动到Tomcat的lib目录,减少重复加载。
    常用方法:

    kill -3 PID
    Heap
     PSYoungGen      total 17024K, used 10442K [0x00007f9e67560000, 0x00007f9e69000000, 0x00007f9e69000000)
      eden space 9088K, 27% used [0x00007f9e67560000,0x00007f9e677d6410,0x00007f9e67e40000)
      from space 7936K, 99% used [0x00007f9e68840000,0x00007f9e68ffc7b0,0x00007f9e69000000)
      to   space 9088K, 0% used [0x00007f9e67e40000,0x00007f9e67e40000,0x00007f9e68720000)
     PSOldGen        total 54656K, used 45409K [0x00007f9e64000000, 0x00007f9e67560000, 0x00007f9e67560000)
      object space 54656K, 83% used [0x00007f9e64000000,0x00007f9e66c58620,0x00007f9e67560000)
     PSPermGen       total 59968K, used 58914K [0x00007f9e5ec00000, 0x00007f9e62690000, 0x00007f9e64000000)
      object space 59968K, 98% used [0x00007f9e5ec00000,0x00007f9e62588ac8,0x00007f9e62690000)
    

    PSPermGen-使用率已达到98%,需要加到PermSize

    jmap -head PID
    [root@chances bin]# ./jmap -heap 12379
    Attaching to process ID 12379, please wait...
    Debugger attached successfully.
    Server compiler detected.
    JVM version is 17.0-b16
    
    using thread-local object allocation.
    Parallel GC with 6 thread(s)
    
    Heap Configuration:
       MinHeapFreeRatio = 40
       MaxHeapFreeRatio = 70
       MaxHeapSize      = 83886080 (80.0MB)
       NewSize          = 1310720 (1.25MB)
       MaxNewSize       = 17592186044415 MB
       OldSize          = 5439488 (5.1875MB)
       NewRatio         = 2
       SurvivorRatio    = 8
       PermSize         = 20971520 (20.0MB)
       MaxPermSize      = 88080384 (84.0MB)
    
    Heap Usage:
    PS Young Generation
    Eden Space:
       capacity = 9306112 (8.875MB)
       used     = 5375360 (5.1263427734375MB)
       free     = 3930752 (3.7486572265625MB)
       57.761608714788736% used
    From Space:
       capacity = 9306112 (8.875MB)
       used     = 3425240 (3.2665634155273438MB)
       free     = 5880872 (5.608436584472656MB)
       36.80634834397007% used
    To Space:
       capacity = 9306112 (8.875MB)
       used     = 0 (0.0MB)
       free     = 9306112 (8.875MB)
       0.0% used
    PS Old Generation
       capacity = 55967744 (53.375MB)
       used     = 48354640 (46.11457824707031MB)
       free     = 7613104 (7.2604217529296875MB)
       86.39733629427693% used
    PS Perm Generation
       capacity = 62062592 (59.1875MB)
       used     = 60243112 (57.452308654785156MB)
       free     = 1819480 (1.7351913452148438MB)
       97.06831451706046% used
    

    **2. OutOfMemoryError: Java heap space **
    原因:java虚拟机创建的对象太多,虚拟机分配给堆内存空间已经用满了,与Heap space有关。
    处理:
    一:检查程序,看是否有死循环或不必要地重复创建大量对象-修改程序或算法。

    public class OOM {
        public static void main(String[] args) {
            Integer sum1=300000;
            Integer sum2=400000;
            OOM oom = new OOM();
            System.out.println("往ArrayList中加入30w内容");
            oom.javaHeapSpace(sum1);
            oom.memoryTotal();
            System.out.println("往ArrayList中加入40w内容");
            oom.javaHeapSpace(sum2);
            oom.memoryTotal();
        }
        public void javaHeapSpace(Integer sum){
            Random random = new Random();  
            ArrayList openList = new ArrayList();
            for(int i=0;i<sum;i++){
                String charOrNum = String.valueOf(random.nextInt(10));
                openList.add(charOrNum);
            }  
        }
        public void memoryTotal(){
            Runtime run = Runtime.getRuntime();
            long max = run.maxMemory();
            long total = run.totalMemory();
            long free = run.freeMemory();
            long usable = max - total + free;
            System.out.println("最大内存 = " + max);
            System.out.println("已分配内存 = " + total);
            System.out.println("已分配内存中的剩余空间 = " + free);
            System.out.println("最大可用内存 = " + usable);
        }
    }
    执行结果:
    往ArrayList中加入30w内容
    最大内存 = 20447232
    已分配内存 = 20447232
    已分配内存中的剩余空间 = 4032576
    最大可用内存 = 4032576
    往ArrayList中加入40w内容
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOf(Arrays.java:2245)
        at java.util.Arrays.copyOf(Arrays.java:2219)
        at java.util.ArrayList.grow(ArrayList.java:242)
        at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)
        at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)
        at java.util.ArrayList.add(ArrayList.java:440)
        at pers.qingqian.study.seven.OOM.javaHeapSpace(OOM.java:36)
        at pers.qingqian.study.seven.OOM.main(OOM.java:26)
    

    从程序执行结果可明显看出往ArrayList中加入30w内容时内存够用,而当往ArrayList加入40w内容时内存就不够了-抛出异常。

    二: 增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。

    JAVA_OPTS="-Xms256m -Xmx1024m"
    

    常用方法:
    步骤一:获取内存的堆信息
    jmap -dump:format=b,file=mem.dat pid
    步骤二:使用内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
    步骤三:看分析图,查找对应问题

    内存溢出.jpg 点击See stacktrace
    http-8087-140
      at org.apache.jsp.template.runtime.tp_005fhd_005fppjc.ppdh_005fdetail_jsp._jspService(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V (ppdh_005fdetail_jsp.java:213)
    

    找到ppdh_005fdetail_jsp.java 的213行(jsp文件编译后)找到如下代码:

      ArrayList tempEpilist = (ArrayList)pageContext.getAttribute("episodes");
      ArrayList epiList = new ArrayList();
      int  j=0;
      for(int i=0;i<tempEpilist.size();i++){
             EpgEpisode epi = (EpgEpisode)tempEpilist.get(i);   
             if(epi.getEpisodeIndex() != (j+1)){ #这里会因为数据的原因产生死循环,从而导致epiList一直在加内容。
                epiList.add(epi);
                 i--;
             }else{
                 epiList.add(epi);
             }
             j++;
      }
    

    内存泄露
    内存泄漏是指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
    内存泄漏随着被执行的次数越多-最终会导致内存溢出。
    而因程序死循环导致的不断创建对象-只要被执行到就会产生内存溢出。
    内存泄漏常见几个情况
    一:静态集合类
    声明为静态(static)的HashMap、Vector 等集合
    通俗来讲A中有B,当前只把B设置为空,A没有设置为空,回收时B无法回收-因被A引用。
    二:监听器
    监听器被注册后释放对象时没有删除监听器
    三:物理连接
    DataSource.getConnection()建立链接,必须通过close()关闭链接
    四:内部类和外部模块等的引用
    发现它的方式同内存溢出,可再加个实时观察
    jstat -gcutil 7362 2500 70
    重点关注:
    FGC — 从应用程序启动到采样时发生 Full GC 的次数
    FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒)
    FGC次数越多,FGCT所需时间越多-可非常有可能存在内存泄漏

    3. OutOfMemoryError:unable to create new native thread
    原因:线程过多
    那么能创建多少线程呢?这里有一个公式:

    (MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads  
    MaxProcessMemory 指的是一个进程的最大内存  
    JVMMemory         JVM内存  
    ReservedOsMemory  保留的操作系统内存  
    ThreadStackSize      线程栈的大小  
    

    当发起一个线程的创建时,虚拟机会在JVM内存创建一个Thread对象同时创建一个操作系统线程,而这个系统线程的内存用的不是JVMMemory,而是系统中剩下的内存:
    (MaxProcessMemory - JVMMemory - ReservedOsMemory)
    结论:你给JVM内存越多,那么你能用来创建的系统线程的内存就会越少,越容易发生java.lang.OutOfMemoryError: unable to create new native thread。

    操作系统总内存为8G、JVM中分配内存4G/6G、保留内存345M、ThreadStackSize 为1M。
    线程数=(8G-4G-345M)/1M=3751
    线程数=(8G-6G-345M)/1M=1703

    cat /var/log/dmesg |grep reserved #可见保留内存
    java -XX:+PrintFlagsInitial  >>1.txt  #打印出所有JVM默认参数和值
    cat 1.txt  | grep ThreadStackSize  #可见线程栈的大小
    

    代码参考:
    http://blog.csdn.net/cutesource/article/details/8244250
    线程数影响:
    http://my.oschina.net/sub/blog/159295

    相关文章

      网友评论

        本文标题:作为测试你应该知道的JAVA OOM及定位分析

        本文链接:https://www.haomeiwen.com/subject/phnpjttx.html