Java虚拟机内存溢出
最近在看周志明的《深入理解Java虛擬機(jī)》,雖然剛剛開始看,但是覺得還是一本不錯(cuò)的書。對于和我一樣對于JVM了解不深,有志進(jìn)一步了解的人算是一本不錯(cuò)的書。注明:不是書托,同樣是華章出的書,質(zhì)量要比《深入剖析Tomcat》高好多,起碼排版上沒有那么多嚴(yán)重的失誤,停,等哪天心情不好再噴那本書。:)(還有一本書讓我看完覺得挺不爽的,當(dāng)然不排除自身問題)
剛剛看了兩章,第一章我比較關(guān)注如何自己編譯openJdk,額,現(xiàn)在還沒搗騰成功,完成后再分享,暫且跳過;本篇文章的主要任務(wù)是記錄書中關(guān)于產(chǎn)生OutOfMemoryError異常的原因。代碼以及說明基本都是出自原書,寫這篇文章意在加深印象,同時(shí)分享給那些沒有讀過這本書的人。說句自己的一次經(jīng)歷,不記得是在哪家公司面試來著,面試官曾經(jīng)問過我都有哪些情況會造成OutOfMemoryError異常。很遺憾,當(dāng)時(shí)我不會。
設(shè)置運(yùn)行時(shí)參數(shù)
說下為什么加了這樣一節(jié),說來慚愧,第一次設(shè)置運(yùn)行時(shí)參數(shù),找不到在哪里設(shè)置,找了半天才找對位置,怕有和我一樣小白的人存在,所以就增加了這樣一個(gè)小節(jié)。(IDE工具是eclipse)
按照如下三步設(shè)置即可,呈現(xiàn)一場代碼的注釋中會標(biāo)注每種情形需要設(shè)置的運(yùn)行時(shí)參數(shù)。
step1:
step2:
step3:
可以這樣為每個(gè)含有main函數(shù)的類指定自己的運(yùn)行時(shí)參數(shù)。
造成內(nèi)存溢出之五大元兇
個(gè)人覺得程序員都要有”刨祖墳”的精神,文藝一點(diǎn)兒就是知其然,知其所以然。在日常的工作中更應(yīng)該如此,不能說要實(shí)現(xiàn)一個(gè)功能就滿口答應(yīng),起碼要知道為什么需要這樣一個(gè)功能,解決什么問題,是否合理。如果連原因都不知道,真心不相信能把這個(gè)功能做好。也許這個(gè)也是好管理和不好管理程序員的分割線。如果說發(fā)生OutOfMemoryError跟我們無關(guān),那我們?yōu)槭裁匆腊l(fā)生的原因啊,美國打伊拉克我和程序員有毛關(guān)系啊。其實(shí)這個(gè)異常對大家來說應(yīng)該都不陌生,之前我最愛的處理就是從新再運(yùn)行一次,不行關(guān)閉eclipse,再不行重啟電腦。(殺手锏級別的解決方案).可是這樣不科學(xué),科學(xué)的方式就要求我們知道為什么會發(fā)生這個(gè)異常,換句話說是發(fā)生這個(gè)異常的場景,然后通過打印出的異常信息快速定位發(fā)生內(nèi)存溢出的區(qū)域,然后進(jìn)行權(quán)衡,調(diào)整運(yùn)行時(shí)參數(shù)來解決。
Java堆溢出
Java堆用于存儲對象實(shí)例,知道這一點(diǎn)就很容易呈現(xiàn)堆溢出,不斷的創(chuàng)建對象,并且保持有指向其的引用,防止為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());}}}通過設(shè)置-Xms20M -Xmx20M都為20M意在防止堆大小自動擴(kuò)展,更好的展現(xiàn)溢出。 執(zhí)行結(jié)果如下:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space是不是很明顯啊,顯示堆空間發(fā)生OutOfMemoryError。
書中告訴我們發(fā)生了OutOfMemoryError后,通常是通過內(nèi)存影像分析工具對dump出來的堆轉(zhuǎn)儲快照進(jìn)行分析(這就是運(yùn)行時(shí)參數(shù)中配置-XX:+HeapDumpOnOutOfMemoryError的原因),重點(diǎn)是確定是由內(nèi)存泄露(Memory Leak)還是有內(nèi)存溢出(Memory Overflow)引起的OutOfMemoryError。如果是內(nèi)存泄露則找到泄露點(diǎn),修正;如果確實(shí)是合理的存在,那么就增加堆空間。(內(nèi)存分析這里我也木有做過,工具也木有使用過,在后續(xù)章節(jié)會有介紹,用熟了后再來一篇)
虛擬機(jī)棧和本地方法棧溢出
由于在HotSpot虛擬機(jī)中并不區(qū)分虛擬機(jī)棧和本地方法區(qū)棧,因此對于HotSpot來說,-Xoss(設(shè)置本地方法棧大小)參數(shù)是無效的,棧容量由-Xss參數(shù)設(shè)定。關(guān)于虛擬機(jī)棧和本地方法區(qū)棧,在Java虛擬機(jī)規(guī)范中描述了兩種異常:
- 如果線程請求的棧深度大于虛擬機(jī)所允許的最大深度,將拋出StackOverflowError異常
- 如果虛擬機(jī)在擴(kuò)展棧時(shí)無法申請到足夠的內(nèi)存空間,則拋出OutOfMemoryError異常
書中談到單線程的場景下只能浮現(xiàn)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設(shè)置虛擬機(jī)棧大小為128k,執(zhí)行結(jié)果如下:
執(zhí)行結(jié)果顯示,確實(shí)是發(fā)生了StackOverflowError異常。
通過不斷創(chuàng)建線程耗盡內(nèi)存也可以呈現(xiàn)出OutOfMemoryError異常,但是在Windows平臺下模擬會使系統(tǒng)死機(jī),我這里就不多說了。感興趣的可以自己去嘗試。
運(yùn)行時(shí)常量池溢出
向運(yùn)行時(shí)常量池中添加內(nèi)容最簡單的方式就是使用String.intern()方法。由于常量池分配在方法區(qū)內(nèi),可以通過-XX:PermSize和-XX:MaxPermSize限制方法區(qū)的大小,從而間接限制其中常量池的容量。
代碼如下:
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());}}}這里有個(gè)小小的插曲,之前有聽說在jdk7中將永久區(qū)(方法區(qū)和常量池)給干掉了,沒有驗(yàn)證過。永久區(qū)可以說是在堆之上的一個(gè)邏輯分區(qū)。如果jdk7中去掉了,那么這個(gè)示例應(yīng)該會拋出堆空間的內(nèi)存溢出,而非運(yùn)行時(shí)常量池的內(nèi)存溢出。所以在執(zhí)行程序的時(shí)候分別用了jdk6和jdk7兩個(gè)版本。多說一句,如果jdk7去掉了方法區(qū),那么-XX:PermSize=10M -XX:MaxPermSize=10M就不起作用了,所以在jdk7環(huán)境下運(yùn)行時(shí),堆大小為jvm默認(rèn)的大小,要執(zhí)行一會兒(半小時(shí)左右:( ))才能拋出異常。沒關(guān)系,再配置下運(yùn)行時(shí)參數(shù)即可,注意要配置成不可擴(kuò)展。以圖為據(jù):
- jdk6環(huán)境下拋出運(yùn)行時(shí)常量池內(nèi)存溢出 Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
顯而易見PermGen space,永久區(qū)。不解釋。
- jdk7環(huán)境下,運(yùn)行時(shí)參數(shù)為:-XX:PermSize=10M -XX:MaxPermSize=10M Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
運(yùn)行了好久好久,最終拋出堆內(nèi)存溢出。Java heap space已經(jīng)足夠說明問題了。
- jdk7環(huán)境下,運(yùn)行時(shí)參數(shù)為:-verbose:gc -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
同樣也是堆內(nèi)存溢出,不過速度就快了好多好多,因?yàn)槎汛笮”辉O(shè)置為不可擴(kuò)展。
方法區(qū)溢出
方法區(qū)用于存放Class的相關(guān)信息,如類名、訪問修飾符、常量池、字段描述、方法描述等。測試這個(gè)區(qū)域只要在運(yùn)行時(shí)產(chǎn)生大量的類填滿方法區(qū),知道溢出。書中借助CGlib直接操作字節(jié)碼運(yùn)行時(shí),生成了大量的動態(tài)類。
當(dāng)前主流的Spring和Hibernate對類進(jìn)行增強(qiáng)時(shí),都會使用到CGLib這類字節(jié)碼技術(shù),增強(qiáng)的類越多,就需要越大的方法區(qū)來保證動態(tài)生成的Class可以加載到內(nèi)存。
測試代碼如下:
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。
方法區(qū)的內(nèi)存溢出問題同樣存在jdk6和jdk7版本之間的區(qū)別,同運(yùn)行時(shí)常量池內(nèi)存溢出。
方法區(qū)溢出也是一種常見的內(nèi)存溢出異常,一個(gè)類如果要被垃圾收集器回收掉,判定條件是非常苛刻的。在經(jīng)常動態(tài)生成大量Class的應(yīng)用中,需要特別注意類的回收狀況。這類場景除了上面提到的程序使用了CGLib字節(jié)碼增強(qiáng)外,常見的還有:大量JSP或動態(tài)生成JSP文件的應(yīng)用、基于OSGi的應(yīng)用等。
本機(jī)直接內(nèi)存溢出
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 stubField unsafeField = Unsafe.class.getDeclaredFields()[0];unsafeField.setAccessible(true);Unsafe unsafe = (Unsafe) unsafeField.get(null);while(true){unsafe.allocateMemory(_1MB);}}}運(yùn)行結(jié)果如下圖:
拋出內(nèi)存溢出異常。不解釋。
總結(jié)
以上是生活随笔為你收集整理的Java虚拟机内存溢出的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Tomcat调节
- 下一篇: Java 注解 拦截器