? 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 }
?