在實驗之前先溫習一下java虛擬機內存分區和對象訪問方式。首先java虛擬機的內存分爲程序計數器PC、虛擬機棧、本地方法棧、堆、方法區、運行時常量池、直接內存七大部分,其中運行時常量池屬於方法區的一部分,直接內存不屬於虛擬機運行時數據區,是本機真實內存的一部分。各個部分的內存超出最大允許範圍並且無法擴展時就會拋出內存溢出錯誤。通過學習對象的訪問方式我們知道,聲明並實例化一個對象時,會在虛擬機棧上分配一塊內存保存指向對象的reference,在堆中保存對象的實例化數據,在方法區中保存對象類型數據。好了下面開始試驗。
試驗一:虛擬機棧和本地方法棧溢出
在HotSpot虛擬機中並不區分虛擬機棧和本地方法棧,對於HotSpot虛擬機-Xoss(設置本地方法棧大小)參數雖存在,但實際上無效,棧容量只由-Xss參數設定。在java虛擬機中當線程請求的棧深度大於虛擬機所允許的最大深度,將拋出StackOverflowError異常,如果虛擬機在擴展棧時無法申請足夠的內存空間,則拋出OutOfMemoryError異常。
實驗代碼:
public class StackOverflowTest{ private static class VmStackSOF{ private int stackLength=1; public void stackLeak(){ stackLength++; stackLeak(); } } public static void main(String[] args){ VmStackSOF sof=new VmStackSOF(); try { sof.stackLeak(); } catch (Throwable e) { e.printStackTrace(); System.out.println("sof.stackLength="+sof.stackLength); } } }
編譯後,執行命令:java -Xss1k -XX:+HeapDumpOnOutOfMemoryError StackOverflowTest
此處通過-Xss參數將虛擬棧大小設爲1k,-XX:+HeapDumpOnOutOfMemoryError參數輸出堆棧信息
執行命令後輸出如下:
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
at StackOverflowTest$VmStackSOF.stackLeak(StackOverflowTest.java:84)
sof.stackLength=18727
實驗二:java堆溢出
通過不停的創建對象並阻止GC垃圾回收的方式來製造java堆內存溢出
實驗代碼:
import java.util.List; import java.util.ArrayList; public class HeapOverflowTest{ public static void main(String[] args){ List<Object> list = new ArrayList<Object>(); while(true){ list.add(new Object()); } } }
編譯後執行命令:java -Xms10m -Xmx10m HeapOverflowTest
通過命令-Xms和-Xmx將虛擬機堆大小設置爲10M,執行命令後輸出如下:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at HeapOverflowTest.main(HeapOverflowTest.java:8)
實驗三:運行時常量池溢出
本文通過不斷向運行時常量池中添加內容的方法制造運行時常量池溢出
實驗代碼:
import java.util.List; import java.util.ArrayList; 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()); } } }
注:在8.0以後的jdk已經移除了-XX:PermSize和-XX:MaxPermSize參數,本文采用-Xmx參數設置最大堆爲10M。
編譯後執行命令:java -Xmx10m RuntimeConstantPoolOOM
輸出如下:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.lang.Integer.toString(Integer.java:401)
at java.lang.String.valueOf(String.java:3099)
at RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:9)
可以看到GC overhead limit exceeded信息,此信息含義是虛擬機花了大部分時間做垃圾回收處理但實際回收的內存小的可憐,當然了,因爲對象分配到了運行時常量池中且引用都沒有失效,所以最終導致了內存溢出異常。
好啦,今天暫時寫到這裏,明天繼續試驗方法區溢出和本地內存溢出試驗。晚安啦!