深入理解JVM閱讀筆記-內存溢出小結
JAVA系統除了程序計數器和虛擬機內存之外的其它幾個內存區域都有發生OutOfMemory(OOM)的可能。堆,棧,方法區,靜態常量池,直接內存,都是可能的。
1.Java堆溢出
Java堆用於存儲對象實例,只要不斷的創建對象,並且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象,那麽在堆達到最大容量的限制後就會產生內存溢出異常。
-Xms -Xmx 參數可以設置Java堆的大小(實際使用中一般Xms和Xmx的大小一致,放置堆自動擴展),通過參數-XX:+HeapDumpOnOutOfMemoryError讓虛擬機在出現內存溢出的異常時Dump出當前的內存堆轉儲快照以便事後進行分析。
當Java出現堆溢出的時候,異常的堆棧信息為“java.lang.OutOfMemberError” 後會跟著進一步提示“java heap space”
下面有一個堆溢出的OOM例子:
/** * -verbose:gc -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError * -XX:+PrintGCDetails -XX:SurvivorRatio=8 * @author scl * */ public class HeapOOM { static class OOMObject { }public static void main(String[] args) { List<OOMObject> list = new ArrayList<OOMObject>(); while(true){ list.add(new OOMObject()); } } }
下方為執行結果:
可以看到出現了一個堆溢出,並產生了一個dump
要解決這個區域的異常需要借助內存映像分析工具對轉儲快照進行分析,確認內存中的對象是否是必要的。也就是確認到底出現了內存泄露(Member Leak)還是內存溢出(Member Overflow)。
如果存在內存泄露,可以通過內存分析工具查看泄露的對象到GC Roots的引用.--暫時不理解如何查看引用
如下,可以推測出16M的空間被一個Object持有,對應的該對象內部都是OOMObject構成的,很自然的聯想到List<OOMObject> list = new ArrayList<OOMObject>();對象.
如果是內存溢出那麽就需要檢查-Xms 和-Xmx大小,本機物理內存大小,或者其他調優。
2.虛擬機棧和本地方法棧溢出
棧容量由-Xss參數設定,以下是測試案例:
/** * -verbose:gc -Xms20M -Xmx20M -Xss128K * -XX:+PrintGCDetails -XX:SurvivorRatio=8 * @author scl * */ public class JavaVMStackSOF { private int stackInteger = 1; public void stackLeak(){ stackInteger++; stackLeak(); } public static void main(String[] args) throws Throwable{ JavaVMStackSOF oom = new JavaVMStackSOF(); try{ oom.stackLeak(); }catch (Throwable e) { // TODO: handle exception System.out.println("stack length :"+oom.stackInteger); throw e; } } }
下方是結果截圖:
這裏順便提一下本來我在這裏異常捕獲的時候使用的是Exception ,結果不會打印出stack length 所以表示這個StackOverflowError不是Exception。而使用Throwable即可捕獲並打印出棧深度。
這裏測試使用的是單線程的情況,如果是多線程的情況下,每個棧分配的內存越大,反而更容易產生內存溢出異常。原因為棧的大小為:操作系統內存-Xmx(最大堆容量)-MaxPermSize(最大方法區容量)-程序計數器(可以忽略不計)。所以在操作系統內存恒定的情況下,每個線程分配的棧容量越大,可以創建的線程數就越少,對應的創建一個新的線程就越容易發生內存溢出。當系統內存溢出又無法減少線程數或者加大總內存的情況下可以嘗試減少棧的-Xss值創建新的線程。
/** * -verbose:gc -Xms20M -Xmx20M -Xss2M * @author scl * */ public class JavaVMStackOOM { private void dontStop(){ while(true){ } } public void stackLeakByThread(){ while(true){ Thread t = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub dontStop(); } }); t.start(); } } public static void main(String[] args) { JavaVMStackOOM oom = new JavaVMStackOOM(); oom.stackLeakByThread(); } }
以上代碼執行具有風險,會造成操作系統假死,如果想要嘗試請先保存當前所有文件。
3.方法區和內存常量池溢出
由於內存常量池包含在方法區內部,所以可以使用-XX:PermSize和-XX:MaxPermSize限制方法區大小,從而間接限制內存常量池的大小。
/** * -XX:PermSize=10M -XX:MaxPermSize=10M * @author scl * */ 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()); } } }
異常信息如下
Sting. Intern方法是一個Native方法,如果字符串常量池中已經包含一個等於該String對象的字符串,則返回代表池中這個字符串的String對象,否則將此String對象添加到常量池中,並返回此String對象的引用.此處OOM後跟著PermGen space也證明了HotSpot的常量池屬於方法區的一部分。
4.本機直接內存溢出--沒有完成測試
DircetMemory容量可以通過-XX:MaxDirectMemorySize指定,如果不指定則與JAVA堆的最大值(-Xmx)一樣。
DircetMemory造成的內存溢出,一個明顯的特征是Heap Dump文件會看不到任何明顯的異常,如果發現OOM之後的dump文件很小,那麽可以考慮是不是這方面的問題。
深入理解JVM閱讀筆記-內存溢出小結