OutOfMemoryError詳解

絮絮不休

最近在看周志明的《深刻理解Java虛擬機》,雖然剛剛開始看,可是以爲仍是一本不錯的書。對於和我同樣對於JVM瞭解不深,有志進一步瞭解的人算是一本不錯的書。註明:不是書託,一樣是華章出的書,質量要比《深刻剖析Tomcat》高好多,起碼排版上沒有那麼多嚴重的失誤,停,等哪天心情很差再噴那本書。:)(還有一本書讓我看完以爲挺不爽的,固然不排除自身問題)java

剛剛看了兩章,第一章我比較關注如何本身編譯openJdk,額,如今還沒搗騰成功,完成後再分享,暫且跳過;本篇文章的主要任務是記錄書中關於產生OutOfMemoryError異常的緣由。代碼以及說明基本都是出自原書,寫這篇文章意在加深印象,同時分享給那些沒有讀過這本書的人。說句本身的一次經歷,不記得是在哪家公司面試來着,面試官曾經問過我都有哪些狀況會形成OutOfMemoryError異常。很遺憾,當時我不會。程序員

設置運行時參數

說下爲何加了這樣一節,說來慚愧,第一次設置運行時參數,找不到在哪裏設置,找了半天才找對位置,怕有和我同樣小白的人存在,因此就增長了這樣一個小節。(IDE工具是eclipse)面試

按照以下三步設置便可,呈現一場代碼的註釋中會標註每種情形須要設置的運行時參數。eclipse

step1:
step2:

step3:


能夠這樣爲每一個含有main函數的類指定本身的運行時參數。jvm

形成內存溢出之五大元兇

我的以爲程序員都要有」刨祖墳」的精神,文藝一點兒就是知其然,知其因此然。在平常的工做中更應該如此,不能說要實現一個功能就滿口答應,起碼要知道爲何須要這樣一個功能,解決什麼問題,是否合理。若是連緣由都不知道,真心不相信能把這個功能作好。也許這個也是好管理和很差管理程序員的分割線。若是說發生OutOfMemoryError跟咱們無關,那咱們爲何要知道發生的緣由啊,美國打伊拉克我和程序員有毛關係啊。其實這個異常對你們來講應該都不陌生,以前我最愛的處理就是重新再運行一次,不行關閉eclipse,再不行重啓電腦。(殺手鐗級別的解決方案).但是這樣不科學,科學的方式就要求咱們知道爲何會發生這個異常,換句話說是發生這個異常的場景,而後經過打印出的異常信息快速定位發生內存溢出的區域,而後進行權衡,調整運行時參數來解決。函數

Java堆溢出

Java堆用於存儲對象實例,知道這一點就很容易呈現堆溢出,不斷的建立對象,而且保持有指向其的引用,防止爲gc。工具

代碼以下:測試

import java.util.ArrayList;
  import java.util.List;

  /**
   * VM Args:-Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
   *
   */
  public class HeapOOM {
    
    static class OOMObject{
      
    }
    
    public static void main(String[] args) {
      List<OOMObject> list = new ArrayList<OOMObject>();
      
      while(true){
        list.add(new OOMObject());
      }
    }

  }

經過設置-Xms20M -Xmx20M都爲20M意在防止堆大小自動擴展,更好的展示溢出。 執行結果以下:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

是否是很明顯啊,顯示堆空間發生OutOfMemoryError。spa

書中告訴咱們發生了OutOfMemoryError後,一般是經過內存影像分析工具對dump出來的堆轉儲快照進行分析(這就是運行時參數中配置-XX:+HeapDumpOnOutOfMemoryError的緣由),重點是肯定是由內存泄露(Memory Leak)仍是有內存溢出(Memory Overflow)引發的OutOfMemoryError。若是是內存泄露則找到泄露點,修正;若是確實是合理的存在,那麼就增長堆空間。(內存分析這裏我也木有作過,工具也木有使用過,在後續章節會有介紹,用熟了後再來一篇).net

虛擬機棧和本地方法棧溢出

因爲在HotSpot虛擬機中並不區分虛擬機棧和本地方法區棧,所以對於HotSpot來講,-Xoss(設置本地方法棧大小)參數是無效的,棧容量由-Xss參數設定。關於虛擬機棧和本地方法區棧,在Java虛擬機規範中描述了兩種異常:

  • 若是線程請求的棧深度大於虛擬機所容許的最大深度,將拋出StackOverflowError異常
  • 若是虛擬機在擴展棧時沒法申請到足夠的內存空間,則拋出OutOfMemoryError異常
  • 書中談到單線程的場景下只能浮現StackOverflowError,那咱們就先來看看單線程場景下到底會是什麼樣子。

    /**
     * 
     * VM Args:-Xss128k
     */
    public class JavaVMStackSOF {
      private int stackLength = 1;
      private void stackLeak() {
        stackLength++;
        stackLeak();
      }
      public static void main(String[] args) throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
          oom.stackLeak();
        } catch (Throwable e) {
          System.out.println("stack length:" + oom.stackLength);
          throw e;
        }
      }
    }

    經過-Xss128k設置虛擬機棧大小爲128k,執行結果以下:

    執行結果顯示,確實是發生了StackOverflowError異常。

    經過不斷建立線程耗盡內存也能夠呈現出OutOfMemoryError異常,可是在Windows平臺下模擬會使系統死機,我這裏就很少說了。感興趣的能夠本身去嘗試。

    運行時常量池溢出

    向運行時常量池中添加內容最簡單的方式就是使用String.intern()方法。因爲常量池分配在方法區內,能夠經過-XX:PermSize和-XX:MaxPermSize限制方法區的大小,從而間接限制其中常量池的容量。

    代碼以下:

    import java.util.ArrayList;
      import java.util.List;
    
      /**
       * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
       * 
       */
      public class RuntimeConstantPoolOOM {
    
        public static void main(String[] args) {
    
          List<String> list = new ArrayList<String>();
    
          int i = 0;
    
          while (true) {
            list.add(String.valueOf(i++).intern());
          }
        }
    
      }

    這裏有個小小的插曲,以前有據說在jdk7中將永久區(方法區和常量池)給幹掉了,沒有驗證過。永久區能夠說是在堆之上的一個邏輯分區。若是jdk7中去掉了,那麼這個示例應該會拋出堆空間的內存溢出,而非運行時常量池的內存溢出。因此在執行程序的時候分別用了jdk6和jdk7兩個版本。多說一句,若是jdk7去掉了方法區,那麼-XX:PermSize=10M -XX:MaxPermSize=10M就不起做用了,因此在jdk7環境下運行時,堆大小爲jvm默認的大小,要執行一下子(半小時左右:( ))才能拋出異常。不要緊,再配置下運行時參數便可,注意要配置成不可擴展。以圖爲據:

  • jdk6環境下拋出運行時常量池內存溢出
  • Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

    顯而易見PermGen space,永久區。不解釋。

  • jdk7環境下,運行時參數爲:-XX:PermSize=10M -XX:MaxPermSize=10M

  • Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

    運行了很久很久,最終拋出堆內存溢出。Java heap space已經足夠說明問題了。

  • jdk7環境下,運行時參數爲:-verbose:gc -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError

  • Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

    一樣也是堆內存溢出,不過速度就快了好多好多,由於堆大小被設置爲不可擴展。

    方法區溢出

    方法區用於存放Class的相關信息,如類名、訪問修飾符、常量池、字段描述、方法描述等。測試這個區域只要在運行時產生大量的類填滿方法區,知道溢出。書中藉助CGlib直接操做字節碼運行時,生成了大量的動態類。

    當前主流的Spring和Hibernate對類進行加強時,都會使用到CGLib這類字節碼技術,加強的類越多,就須要越大的方法區來保證動態生成的Class能夠加載到內存。

    測試代碼以下:

    import net.sf.cglib.proxy.Enhancer;
      import net.sf.cglib.proxy.MethodInterceptor;
      import net.sf.cglib.proxy.MethodProxy;
    
      /**
       * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
       *
       */
      public class JavaMethodAreaOOM {
        public static void main(String[] args) {
          while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
              public Object intercept(Object object, Method method,
                  Object[] args, MethodProxy proxy) throws Throwable{
                return proxy.invokeSuper(object, args);
              }
            });
            enhancer.create();
          }
    
        }
    
        static class OOMObject {
    
        }
    
      }

    工程中要引入cglib-2.2.2.jar和asm-all-3.3.jar。

    方法區的內存溢出問題一樣存在jdk6和jdk7版本之間的區別,同運行時常量池內存溢出。

    方法區溢出也是一種常見的內存溢出異常,一個類若是要被垃圾收集器回收掉,斷定條件是很是苛刻的。在常常動態生成大量Class的應用中,須要特別注意類的回收情況。這類場景除了上面提到的程序使用了CGLib字節碼加強外,常見的還有:大量JSP或動態生成JSP文件的應用、基於OSGi的應用等。

    本機直接內存溢出

    DirectMemory容量能夠經過-XX:MaxDirectMemorySize指定。

    示例代碼以下:

    import java.lang.reflect.Field;
      import sun.misc.Unsafe;
    
      /**
       * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
       *
       */
      public class DirectMemoryOOM {
    
        private static final int _1MB = 1024 * 1024;
    
        /**
         * @param args
         * @throws IllegalAccessException
         * @throws IllegalArgumentException
         */
        public static void main(String[] args) throws IllegalArgumentException,
            IllegalAccessException {
          // TODO Auto-generated method stub
          Field unsafeField = Unsafe.class.getDeclaredFields()[0];
          unsafeField.setAccessible(true);
          Unsafe unsafe = (Unsafe) unsafeField.get(null);
          
          while(true){
            unsafe.allocateMemory(_1MB);
          }
        }
    
      }

    運行結果以下圖:

    拋出內存溢出異常。不解釋。

     本文出自:http://www.congmo.net/blog/2012/07/01/java-outofmemory/

    相關文章
    相關標籤/搜索