java虛擬機在執行java程序的過程當中將它所管理的內存劃分爲如下幾個運行時數據區域:java
線程私有區域(程序計數器、虛擬機棧、本地方法棧),線程共享區域(堆、方法區),直接內存。線程私有數據區域生命週期與線程相同, 依賴用戶線程的啓動/結束 而 建立/銷燬(在 Hotspot VM 內)。線程共享區域隨虛擬機的啓動/關閉而建立/銷燬。直接內存並非 JVM 運行時數據區的一部分。算法
程序計數器是當前線程執行的字節碼的行號指示器,爲了線程切換後可以恢復到正確的執行位置,每條線程都須要一個獨立的程序計數器,個線程之間的程序計數器互不影響,各自獨立存儲。這類內存區域爲「線程私有」的區域。此內存區域是惟一一個沒有OutOfMemoryError的區域。數組
虛擬機棧描述的是Java方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀(Stack Frame)用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每一個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。棧容量只由-Xss參數設定jvm
/** * VM Args: -Xss160k * -Xss的最小值爲160k **/ public class JavaVMStackSOF { private int stackLength = 1; public 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; } } }
程序輸出:工具
stack length : 772 Exception in thread "main" java.lang.StackOverflowError at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11) at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
本地方法棧與虛擬機棧的做用類似,區別在於虛擬機方法棧爲虛擬機執行Java方法服務,本地方法棧爲虛擬機使用Native方法服務。Hotspot虛擬機將本地方法棧和虛擬機方法棧合二爲一。開發工具
Java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。全部建立的對象和數組都存放在堆上。Java堆是垃圾收集器管理的主要區域,垃圾收集器主要基於**分代收集算法**,所以從GC的角度對堆進行劃分:新生代(Eden空間、From Survivor空間、To Survivor空間)、老年代。若是在堆中沒有內存完成示例分配、而且堆也沒法再擴展,將會拋出OutOfMemoryError異常。堆的大小能夠經過-Xmx(jvm運行時堆的最大值)和-Xms(jvm運行時堆的最小值)控制。測試
如下代碼測試堆的OutOfMemoryError異常,將堆的最小值-Xms參數與最大值-Xmx參數設置爲同樣便可避免堆自動擴展,參數-XX:+HeapDumpOnOutOfMemoryError可以讓虛擬機在出現內存溢出是Dump出當前的內存堆轉儲快照以便時候進行分析。spa
/** * 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()); } } }
輸出以下:線程
java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid3421.hprof ... Heap dump file created [27644178 bytes in 0.129 secs] 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 HeapOOM.main(HeapOOM.java:18)
方法區用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。code
運行時常量池(Runtime Constant Pool)是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池(Constant Pool Table),用於存放編譯期生成的各類字面量和符號引用,這部份內容將在類加載後進入方法區的運行時常量池。java虛擬機對class文件每一部分的格式都有嚴格的規定,每個字節用於存儲哪一種數據都必須符號規範上的要求才會被虛擬機承認、裝載和執行。
既然運行時常量池是方法區的一部分,天然受到方法區內存的限制,當常量池沒法再申請到內存時會拋出OutOfMemoryError的異常,測試方法區和運行時常量池溢出:
/** * jdk 1.8及以後,VM Args: -XX:MetaspaceSize=2m -XX:MaxMetaspaceSize=2m * jdk 1.8及以前,VM Args: -XX:PermSize=2m -XX:MaxPermSize=2m **/ public class RuntimeConstantPoolOOM { public static void main(String[] args) { List<String> list = new ArrayList<>(); int i = 0; while (true) { list.add(String.valueOf(i++).intern()); } } }
程序輸入:
Error occurred during initialization of VM
java.lang.OutOfMemoryError: Metaspace
<<no stack trace available>>
直接內存(Direct Memory)並非虛擬機運行時的數據區的一部分,也不是java虛擬機規範中定義的內存區域,不會受到java堆大小的限制,不受jvm gc的管理。這部份內存若是頻繁使用,也可能致使OutOfMemoryError異常, 測試本機直接內存溢出,DirectMemory容量可經過-XX:MaxDirectMemorySize設置,若是不指定,默認與Java堆最大值(-Xmx指定)同樣
/** * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M **/ public class DirectMemoryOOM { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws Exception { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while (true){ unsafe.allocateMemory(_1MB); } } }
經過測試,始終沒有拋出OutOfMemoryError異常,測試環境jdk1.8, 開發工具IDEA, 目前還不知道爲何,若是有哪位朋友知道爲何,你們一塊兒討論下