深入理解JVM之OOM

541732025發表於2015-03-10
做Java程式開發,難免會遇到OutOfMemory,導致的原因也是不盡相同,下面我們來捋一捋OOM出現的場景。
一,堆空間不足,這是最容易,也是最常見的OOM。瞭解Java記憶體結構的同學應該知道,Java裡面建立的物件大部分都是位於堆空間的,當建立的物件太多,而堆空間不足時,很容易丟擲OOM,如下:

點選(此處)摺疊或開啟

  1. import java.util.ArrayList;
  2. import java.util.List;

  3. /*VM args:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
  4.  *
  5.  * -Xms:堆的最小值;-Xmx:堆的最大值
  6.  *
  7.  * */
  8. public class HeapOOM {

  9.     static class OOMObject {
  10.     }

  11.     public static void main(String[] args) {
  12.         List<OOMObject> list = new ArrayList<OOMObject>();
  13.         while (true) {
  14.             list.add(new OOMObject());
  15.         }
  16.     }
  17. }
執行結果:

點選(此處)摺疊或開啟

  1. Exception in thread \"main\" java.lang.OutOfMemoryError: Java heap space
  2.     at java.util.Arrays.copyOf(Arrays.java:2760)
  3.     at java.util.Arrays.copyOf(Arrays.java:2734)
  4.     at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
  5.     at java.util.ArrayList.add(ArrayList.java:351)
  6.     at test.java.VM.OOM.HeapOOM.main(HeapOOM.java:19)

二,直接分配記憶體溢位,Java提供了一些可以直接操作記憶體和執行緒的低層次操作(native)-sun.misc.Unsafe,其被JDK廣泛用於自己的包中,如java.nio和java.util.concurrent。
但是絲毫不建議在生產環境中使用這個Unsafe,從名字就可以看出,這個API十分不安全、不輕便、而且不穩定。

點選(此處)摺疊或開啟

  1. import java.lang.reflect.Field;

  2. import sun.misc.Unsafe;

  3. /*VM args:-Xmx10m -XX:MaxDirectMemorySize=5m
  4.  *
  5.  * */
  6. public class DirectMemoryOOM {
  7.     private static final int _1MB = 1024 * 1024;

  8.     public static void main(String[] args) throws Exception {
  9.         Field unsafeField = Unsafe.class.getDeclaredField(\"theUnsafe\");
  10.         unsafeField.setAccessible(true);
  11.         Unsafe unsafe = (Unsafe) unsafeField.get(null);

  12.         while (true) {
  13.             unsafe.allocateMemory(_1MB);
  14.         }

  15.     }

  16. }
執行結果:

點選(此處)摺疊或開啟

  1. Exception in thread \"main\" java.lang.OutOfMemoryError
  2.     at sun.misc.Unsafe.allocateMemory(Native Method)
  3.     at test.java.VM.OOM.DirectMemoryOOM.main(DirectMemoryOOM.java:19)

三,方法區溢位,方法區主要存放類的資訊、靜態變數、Field、Method資訊等,當不停地有類動態建立並載入時,方法區也能產生OOM。

點選(此處)摺疊或開啟

  1. import java.lang.reflect.Method;

  2. import net.sf.cglib.proxy.Enhancer;
  3. import net.sf.cglib.proxy.MethodInterceptor;
  4. import net.sf.cglib.proxy.MethodProxy;

  5. /*VM args: -XX:PermSize=5m -XX:MaxPermSize=5m
  6.  *
  7.  * */
  8. public class JavaMethodAreaOOM {
  9.     public static void main(String[] args) {
  10.         while (true) {
  11.             Enhancer enhancer = new Enhancer();
  12.             enhancer.setSuperclass(OOMObject.class);
  13.             enhancer.setUseCache(false);
  14.             enhancer.setCallback(new MethodInterceptor() {
  15.                 @Override
  16.                 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  17.                     //return method.invoke(obj, args);
  18.                     return proxy.invokeSuper(obj, args);
  19.                 }
  20.             });
  21.             OOMObject object = (OOMObject) enhancer.create();
  22.         }

  23.     }

  24.     static class OOMObject {
  25.     }
  26. }
執行結果:

點選(此處)摺疊或開啟

  1. Exception in thread \"main\" java.lang.OutOfMemoryError: PermGen space

四,常量池溢位,常量池屬於方法區,存放一些常量值(如static final),還有一些文字形式出現的符號引用,如:類和介面的全限定名、欄位的名稱和描述符。

點選(此處)摺疊或開啟

  1. import java.util.ArrayList;
  2. import java.util.List;

  3. /*VM rags:-XX:PermSize=5m -XX:MaxPermSize=5m
  4.  *
  5.  *
  6.  * */
  7. public class RuntimeConstantPoolOOM {
  8.     public static void main(String[] args) {
  9.         //使用List保持著常量池引用,避免Full GC回收常量池行為
  10.         List<String> list = new ArrayList<String>();

  11.         int i = 0;
  12.         while (true) {
  13.             list.add(String.valueOf(i++).intern());//將String物件加入常量池
  14.         }
  15.     }
  16. }
執行結果:

點選(此處)摺疊或開啟

  1. Exception in thread \"main\" java.lang.OutOfMemoryError: PermGen space
  2.     at java.lang.String.intern(Native Method)
  3.     at test.java.VM.OOM.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:17)
  4. Exception in thread \"Reference Handler\" java.lang.OutOfMemoryError: PermGen space
  5.     at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:123)

五,Stack溢位,JVM方法棧為執行緒私有,當方法執行時會被建立,當方法執行完畢,其對應的棧幀所佔用的記憶體也會被自動釋放。
當方法棧的深度不足時,會丟擲StackOverflowError,不過只要不出現無窮遞迴,棧的深度不會太大。

點選(此處)摺疊或開啟

  1. /*VM args:-Xss128k
  2.  *
  3.  * 在單執行緒下,無論是由於棧幀太大,還是虛擬機器容量太小,
  4.  * 當記憶體無法分配的時候,虛擬機器丟擲的都是StackOverflowError
  5.  * 如果測試不限於單執行緒,透過不斷建立執行緒的方式倒是可以產生記憶體溢位異常(詳見JavaVMStackOF2)
  6.  * */
  7. public class JavaVMStackOF {
  8.     private int stackLength = 1;

  9.     public void stackLeak() {
  10.         stackLength++;//其實,即使沒有運算元,也會throw StackOverflowError
  11.         stackLeak();
  12.     }

  13.     public static void main(String[] args) throws Throwable {
  14.         JavaVMStackOF oom = new JavaVMStackOF();
  15.         try {
  16.             oom.stackLeak();
  17.         } catch (Throwable e) {
  18.             System.out.println(\"stack length:\" + oom.stackLength);
  19.             throw e;
  20.         }
  21.     }
  22. }
執行結果:

點選(此處)摺疊或開啟

  1. stack length:1007Exception in thread \"main\" java.lang.StackOverflowError

  2.     at test.java.VM.OOM.JavaVMStackOF.stackLeak(JavaVMStackOF.java:13)
  3.     at test.java.VM.OOM.JavaVMStackOF.stackLeak(JavaVMStackOF.java:14)

補充:

點選(此處)摺疊或開啟

  1. /*VM args:-Xss10m
  2.  *
  3.  * 透過不斷建立新執行緒達到OutOfMemoryError
  4.  * 實體記憶體-Xmx(最大堆容量)-MaxPermSize(最大方法區容量)=虛擬機器棧+本地方法棧,程式計數器消耗記憶體較小,可以忽略。
  5.  * -Xss10m,分配給每個執行緒的記憶體。所以-Xss越大,越容易出現OutOfMemoryError(可建立的執行緒越少)。
  6.  *
  7.  * 棧深度在虛擬機器預設情況下,達到1000~2000完全沒問題,對於正常的方法呼叫(包括遞迴),這個深度完全夠用了。
  8.  * 但是,如果是建立過多執行緒導致 的記憶體溢位,在不能減少執行緒數或者更換64位虛擬機器的情況下,就只能透過減少最大堆容量和減少棧容量來換取更多的執行緒。
  9.  * 如果沒有這方面的經驗,這種透過“減少記憶體”的手段來解決記憶體溢位的方式會比較難以想到!
  10.  * */

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28912557/viewspace-1455299/,如需轉載,請註明出處,否則將追究法律責任。

相關文章