• 【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 }
  • 相关阅读:
    对Spring 框架 AOP(面向切面)的理解
    页面自动刷新
    页面通过ajax传值到后台,后台返回值展示在页面输入框
    java中怎么跳出两层for循环
    人的三种思维角度
    我理解的战争(程序员是需要有立场的)
    我所理解的JavaScript中的prototype与__proto__、constructor
    一个"失速"项目的总结
    TDD学习笔记
    Java SQL动态生成库
  • 原文地址:https://www.cnblogs.com/printN/p/6901668.html
Copyright © 2020-2023  润新知