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 }