zoukankan      html  css  js  c++  java
  • 【3】JVM-OutOfMemory异常重现

      JVM中常见的OOM,那么如何通过自己编写代码产生这些OOM异常呢?通过写代码重现异常,是为了避免在工作中写出有OOM BUG的代码。之前虽然看过相关文章,但是没自己写过这些代码,这次在编写的实际过程中,由于和书本使用的JDK版本不一致,也会有点问题。其中印象最深刻的就是从JDK1.7开始常量池就已经不放在方法区了,而是改到了Java堆中,所以《深入理解JAVA虚拟机》中的有些知识也需要更新了。下面的代码基于JDK1.7来的。并且在运行程序的时候需要设置JVM参数,如果不设置,轻则需要等待很长时间才会出现异常,重则系统假死甚至导致系统内存溢出。

        在测试直接内存的时候,引用了rt.jar中的sun.misc.Unsafe类,如果使用了Eclipse作为IDE,需要修改windows-->preferences-->java-->compiler-->Errors/Warinings,选择Deprecated and restricted API,将Forbidden reference(access rules)修改成ignore。

      1 package org.zsl.learn.oom;
      2 
      3 import java.lang.reflect.Field;
      4 import java.lang.reflect.Method;
      5 import java.util.ArrayList;
      6 import java.util.List;
      7 
      8 
      9 import net.sf.cglib.proxy.Enhancer;
     10 import net.sf.cglib.proxy.MethodInterceptor;
     11 import net.sf.cglib.proxy.MethodProxy;
     12 import sun.misc.Unsafe;
     13 
     14 /**
     15  * 测试在代码中如何产生堆内存溢出、栈溢出(超出长度)、栈内存溢出(栈不能扩展的情况下OOM)、方法区内存溢出、常量池内存溢出
     16  * JDK1.7
     17  * @author Administrator
     18  *
     19  */
     20 public class TestOOM {
     21     private static int count = 1;
     22     private static final int _1MB = 1024*1024;
     23     
     24     List<String> list = new ArrayList<String>();
     25     
     26     //一个普通的对象
     27     static class OOMObjectClass{
     28         public OOMObjectClass(){}
     29     }
     30     
     31     /**
     32      * 通过list对象保持对对象列表的引用,不然GC收集对象,然后不断地向列表中添加新的对象,就会发生OOM
     33      * 
     34      * @VM args:-verbose:gc -Xms10M -Xmx10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError
     35      */
     36     public void testHeapOOM(){
     37         List<OOMObjectClass> list = new ArrayList<>();
     38         while(true){
     39             list.add(new OOMObjectClass());
     40         }
     41     }
     42     
     43     /**
     44      * 通过递归调用方法,从而让方法栈产生栈 StackOverflowError
     45      * 
     46      * @VM args:-verbose:gc -Xss128k
     47      */
     48     public void stackLeak(){
     49         count++;
     50         stackLeak();
     51     }
     52     
     53     
     54     /**
     55      * 除了上述的递归调用可以产生溢出外,还有就是过多的线程,当栈内存无法动弹扩展是,会出现OOM
     56      * 
     57      * 由于在Window的JVM中,Jave的线程是映射到了操作系统的内核线程上,故而这段代码的运行时非常危险的
     58      * 笔者运行的时候限制了JVM内存大小,但是栈内存可以动态扩展,所以电脑内存直接到了90%以上,我果断停止了程序的运行
     59      * 由于栈内存只由-Xss参数控制,并没有办法让其不自动扩展,所以这段代码非常危险
     60      * 参数:-verbose:gc -Xms10M -Xmx10M -Xss2M
     61      */
     62     public void stackLeakByThread(){
     63         while(true){
     64             Thread t = new Thread(new Runnable() {
     65                 
     66                 @Override
     67                 public void run() {
     68                     while (true){
     69                         
     70                     }
     71                 }
     72             });
     73             t.start();
     74             count++;
     75         }
     76     }
     77     
     78     /**
     79      * 常量池是存在于方法区内的,故而只要限制了方法区的大小,当不断新增常量的时候就会发生常量池的溢出
     80      * 
     81      * 笔者使用的是JDK1.7 64位,此时的常量池已经不存在与方法区中,而是迁移到了堆中,故而测试的时候需要限制JVM的堆大小,且不能自动扩展
     82      * @VM args: -Xms10M -Xmx10M
     83      */
     84     public void constantPoolOOM(){
     85         int i=0;
     86         while(true){
     87             list.add(String.valueOf(i++).intern()); //String类型的intern方法是将字符串的值放到常量池中
     88         }
     89     }
     90     
     91     /**
     92      * 方法区是存放一些类的信息等,所以我们可以使用类加载无限循环加载class,这样就会出现方法区的OOM异常
     93      * 主要,使用内部类的时候,需要要使用静态内部类,如果使用的是非静态内部类,将不会发生方法区OOM
     94      * 使用了CGLib直接操作字节码运行时,生成了大量的动态类
     95      * 需要者两个jar包:cglib-2.2.2.jar   asm-3.1.jar
     96      * @VM args:-XX:PermSize=10M -XX:MaxPermSize=10M
     97      */
     98     public void methodAreaOOM(){
     99         while(true){
    100             Enhancer eh = new Enhancer();
    101             eh.setSuperclass(OOMObjectClass.class);
    102             eh.setUseCache(false);
    103             eh.setCallback(new MethodInterceptor() {
    104                 @Override
    105                 public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
    106                     return arg3.invokeSuper(arg0, arg2);
    107                 }
    108             });
    109             eh.create();
    110         }
    111     }
    112     
    113     /**
    114      * 要讨论这部分的内存溢出,首先必须要说一下什么是直接内存:
    115      *     直接内存并不是JVM运行时数据区的一部分,也不是JVM规范中定义的内存区域,但是这部分内存也被频繁的使用,也会产生OOM。
    116      *     JDK1.4中新加入了NIO类,引入了一种Channel与Buffer的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在JAVA堆里面的DirectByteBuffer对象作为
    117      *     这些堆外内存的引用进而操作,这样在某些场景中可以显著的提高性能,避免了在native堆和java堆中来回复制数据。这这部分堆外内存就是直接内存了。
    118      * 
    119      * 直接内存虽然不会受到JAVA堆大小的限制,但是还是会受到本机内存大小的限制,故而服务器管理员在设置JVM内存管理参数的时候,如果忘记了直接内存,那么当程序进行动态扩展的时候,就有可能发生OOM
    120      * 直接内存的容量可以通过-XX:MaxDirectMemorySize指定,如果不指定,那么默认与JAVA堆得最大值一样。
    121      * 
    122      * @VM args:-Xmx20M -XX:MaxDirectMemorySize=10M
    123      * @throws SecurityException 
    124      * @throws NoSuchFieldException 
    125      * @throws IllegalAccessException 
    126      * @throws IllegalArgumentException 
    127      */
    128     public void directMemoryOOM() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{
    129         Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
    130         unsafeField.setAccessible(true);
    131         Unsafe unsafe = (Unsafe)unsafeField.get(null);
    132         while(true){
    133             unsafe.allocateMemory(_1MB);
    134         }
    135     }
    136     
    137     
    138     
    139     
    140     public static void main(String[] args) {
    141         TestOOM oom = new TestOOM();
    142 //        ---------测试堆内存溢出-----------
    143 //        oom.testHeapOOM();    
    144         
    145 //        ---------测试栈溢出----------
    146 //        try{
    147 //            oom.stackLeak(); 
    148 //        }catch(Throwable error){
    149 //            System.out.println("Stack length-->"+count);
    150 //            throw error;
    151 //        }
    152         
    153 //        ---------测试由于栈动态扩展导致的OOM----------        
    154 //        try{
    155 //            oom.stackLeakByThread();
    156 //        }catch(Throwable error){
    157 //            System.out.println("Stack length-->"+count);
    158 //            throw error;
    159 //        }
    160         
    161 //        ----------测试方法区溢出----------
    162 //        oom.methodAreaOOM();
    163         
    164 //        ----------测试常量池溢出----------
    165 //        oom.constantPoolOOM();
    166         
    167 //        ----------测试直接内存溢出----------
    168         
    169         try {
    170             oom.directMemoryOOM();
    171         } catch (Exception e) {
    172             System.out.println(e);
    173         }
    174         
    175         
    176         
    177     }
    178     
    179     
    180 }
  • 相关阅读:
    pat 甲级 1065. A+B and C (64bit) (20)
    pat 甲级 1064. Complete Binary Search Tree (30)
    pat 甲级 1010. Radix (25)
    pat 甲级 1009. Product of Polynomials (25)
    pat 甲级 1056. Mice and Rice (25)
    pat 甲级 1078. Hashing (25)
    pat 甲级 1080. Graduate Admission (30)
    pat 甲级 团体天梯 L3-004. 肿瘤诊断
    pat 甲级 1099. Build A Binary Search Tree (30)
    Codeforce 672B. Different is Good
  • 原文地址:https://www.cnblogs.com/printN/p/6901668.html
Copyright © 2011-2022 走看看