最近在看周志明的《深刻理解Java虛擬機》,雖然剛剛開始看,可是以爲仍是一本不錯的書。對於和我同樣對於JVM瞭解不深,有志進一步瞭解的人算是一本不錯的書。註明:不是書託,一樣是華章出的書,質量要比《深刻剖析Tomcat》高好多,起碼排版上沒有那麼多嚴重的失誤,停,等哪天心情很差再噴那本書。:)(還有一本書讓我看完以爲挺不爽的,固然不排除自身問題)java
剛剛看了兩章,第一章我比較關注如何本身編譯openJdk,額,如今還沒搗騰成功,完成後再分享,暫且跳過;本篇文章的主要任務是記錄書中關於產生OutOfMemoryError異常的緣由。代碼以及說明基本都是出自原書,寫這篇文章意在加深印象,同時分享給那些沒有讀過這本書的人。說句本身的一次經歷,不記得是在哪家公司面試來着,面試官曾經問過我都有哪些狀況會形成OutOfMemoryError異常。很遺憾,當時我不會。程序員
說下爲何加了這樣一節,說來慚愧,第一次設置運行時參數,找不到在哪裏設置,找了半天才找對位置,怕有和我同樣小白的人存在,因此就增長了這樣一個小節。(IDE工具是eclipse)面試
按照以下三步設置便可,呈現一場代碼的註釋中會標註每種情形須要設置的運行時參數。eclipse
能夠這樣爲每一個含有main函數的類指定本身的運行時參數。jvm
我的以爲程序員都要有」刨祖墳」的精神,文藝一點兒就是知其然,知其因此然。在平常的工做中更應該如此,不能說要實現一個功能就滿口答應,起碼要知道爲何須要這樣一個功能,解決什麼問題,是否合理。若是連緣由都不知道,真心不相信能把這個功能作好。也許這個也是好管理和很差管理程序員的分割線。若是說發生OutOfMemoryError跟咱們無關,那咱們爲何要知道發生的緣由啊,美國打伊拉克我和程序員有毛關係啊。其實這個異常對你們來講應該都不陌生,以前我最愛的處理就是重新再運行一次,不行關閉eclipse,再不行重啓電腦。(殺手鐗級別的解決方案).但是這樣不科學,科學的方式就要求咱們知道爲何會發生這個異常,換句話說是發生這個異常的場景,而後經過打印出的異常信息快速定位發生內存溢出的區域,而後進行權衡,調整運行時參數來解決。函數
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,那咱們就先來看看單線程場景下到底會是什麼樣子。
/** * * 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默認的大小,要執行一下子(半小時左右:( ))才能拋出異常。不要緊,再配置下運行時參數便可,注意要配置成不可擴展。以圖爲據:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
顯而易見PermGen space,永久區。不解釋。
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
運行了很久很久,最終拋出堆內存溢出。Java heap space已經足夠說明問題了。
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/