Java虚拟机——Java内存区域与内存溢出
內存區(qū)域 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? Java虛擬機在執(zhí)行Java程序的過程中會把他所管理的內存劃分為若干個不同的數(shù)據(jù)區(qū)域。Java虛擬機規(guī)范將JVM所管理的內存分為以下幾個運行時數(shù)據(jù)區(qū):程序計數(shù)器、Java虛擬機棧、本地方法棧、Java堆、方法區(qū)。下面詳細闡述各數(shù)據(jù)區(qū)所存儲的數(shù)據(jù)類型。
(圖片來自:http://blog.csdn.net/ns_code/article/details/17565503)
程序計數(shù)器(Program?Counter?Register)
? ? ? 一塊較小的內存空間,它是當前線程所執(zhí)行的字節(jié)碼的行號指示器,字節(jié)碼解釋器工作時通過改變該計數(shù)器的值來選擇下一條需要執(zhí)行的字節(jié)碼指令,分支、跳轉、循環(huán)等基礎功能都要依賴它來實現(xiàn)。每條線程都有一個獨立的的程序計數(shù)器,各線程間的計數(shù)器互不影響,因此該區(qū)域是線程私有的。
? ? ? 當線程在執(zhí)行一個Java方法時,該計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址,當線程在執(zhí)行的是Native方法(調用本地操作系統(tǒng)方法)時,該計數(shù)器的值為空。另外,該內存區(qū)域是唯一一個在Java虛擬機規(guī)范中么有規(guī)定任何OOM(內存溢出:OutOfMemoryError)情況的區(qū)域。
Java虛擬機棧(Java?Virtual?Machine?Stacks)
? ? ?該區(qū)域也是線程私有的,它的生命周期也與線程相同。虛擬機棧描述的是Java方法執(zhí)行的內存模型:每個方法被執(zhí)行的時候都會同時創(chuàng)建一個棧幀,棧它是用于支持續(xù)虛擬機進行方法調用和方法執(zhí)行的數(shù)據(jù)結構。對于執(zhí)行引擎來講,活動線程中,只有棧頂?shù)臈怯行У?#xff0c;稱為當前棧幀,這個棧幀所關聯(lián)的方法稱為當前方法,執(zhí)行引擎所運行的所有字節(jié)碼指令都只針對當前棧幀進行操作。棧幀用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法返回地址和一些額外的附加信息。在編譯程序代碼時,棧幀中需要多大的局部變量表、多深的操作數(shù)棧都已經完全確定了,并且寫入了方法表的Code屬性之中。因此,一個棧幀需要分配多少內存,不會受到程序運行期變量數(shù)據(jù)的影響,而僅僅取決于具體的虛擬機實現(xiàn)。
? ? ? 在Java虛擬機規(guī)范中,對這個區(qū)域規(guī)定了兩種異常情況:
???? 1、如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常。
???? 2、如果虛擬機在動態(tài)擴展棧時無法申請到足夠的內存空間,則拋出OutOfMemoryError異常。
? ? ? 這里有一點要重點說明,在多線程情況下,給每個線程的棧分配的內存越大,越容易產生內存溢出異常。操作系統(tǒng)為每個進程分配的內存是有限制的,虛擬機提供了參數(shù)來控制Java堆和方法區(qū)這兩部分內存的最大值,忽略掉程序計數(shù)器消耗的內存(很小),以及進程本身消耗的內存,剩下的內存便給了虛擬機棧和本地方法棧,每個線程分配到的棧容量越大,可以建立的線程數(shù)量自然就越少。因此,如果是建立過多的線程導致的內存溢出,在不能減少線程數(shù)的情況下,就只能通過減少最大堆和每個線程的棧容量來換取更多的線程。當由于創(chuàng)建過量線程發(fā)生OOM時,會報錯:java.lang.OutOfMemoryError, unable to create new native thread。
本地方法棧(Native?Method?Stacks)
? ? ? 該區(qū)域與虛擬機棧所發(fā)揮的作用非常相似,只是虛擬機棧為虛擬機執(zhí)行Java方法服務,而本地方法棧則為使用到的本地操作系統(tǒng)(Native)方法服務。與虛擬機棧一樣,本地方法棧區(qū)域也會拋出StackOverflowError與OutOfMemoryError異常。
Java堆(Java?Heap)
? ? ? Java?Heap是Java虛擬機所管理的內存中最大的一塊,它是所有線程共享的一塊內存區(qū)域,幾乎所有的對象實例和數(shù)組都在這類分配內存。Java?Heap是垃圾收集器管理的主要區(qū)域,因此很多時候也被稱為“GC堆”。
? ? ? 根據(jù)Java虛擬機規(guī)范的規(guī)定,Java堆可以處在物理上不連續(xù)的內存空間中,只要邏輯上是連續(xù)的即可。如果在堆中沒有內存可分配時,并且堆也無法擴展時,將會拋出OutOfMemoryError異常。 ??
? ? ? 注意:隨著JIT編譯器的發(fā)展與逃逸技術逐漸成熟,所有對象都分配在堆上也逐漸變得不是那么絕對了,線程共享的Java堆中也可能劃分出線程私有的分配緩沖區(qū)(TLAB)。
方法區(qū)(Method?Area)
????方法區(qū)也是各個線程共享的內存區(qū)域,它用于存儲已經被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。Java虛擬機規(guī)范把方法區(qū)描述為Java堆的一個邏輯部分,而且它和Java?Heap一樣不需要連續(xù)的內存,可以選擇固定大小或可擴展,另外,虛擬機規(guī)范允許該區(qū)域可以選擇不實現(xiàn)垃圾回收。相對而言,垃圾收集行為在這個區(qū)域比較少出現(xiàn)。不過,這部分區(qū)域的回收是有必要的,如果這部分區(qū)域永遠不回收,那么類型就無法卸載,我們就無法加載更多的類,HotSpot的該區(qū)域有實現(xiàn)垃圾回收。
????根據(jù)Java虛擬機規(guī)范的規(guī)定,當方法區(qū)無法滿足內存分配需求時,將拋出OutOfMemoryError異常。
直接內存(Direct Memory)?
? ? ? 直接內存并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機規(guī)范中定義的內存區(qū)域,它直接從操作系統(tǒng)中分配,因此不受Java堆大小的限制,但是會受到本機總內存的大小及處理器尋址空間的限制,因此它也可能導致OutOfMemoryError異常出現(xiàn)。在JDK1.4中新引入了NIO機制,它是一種基于通道與緩沖區(qū)的新I/O方式,可以直接從操作系統(tǒng)中分配直接內存,即在堆外分配內存,這樣能在一些場景中提高性能,因為避免了在Java堆和Native堆中來回復制數(shù)據(jù)。
? ? ? 當使用超過虛擬機允許的直接內存時,虛擬機會拋出OutOfMemoryError異常,由DirectMemory導致的內存溢出,一個明顯的特征是在Heap Dump文件中不會看見明顯的異常。一般來說,如果發(fā)現(xiàn)OOM后Dump文件很小,那就應該考慮一下,是不是這塊內存發(fā)生了溢出。
內存溢出 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
Java堆內存溢出
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,該參數(shù)限制Java堆大小為20M,不可擴展。運行結果如下:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:2245)at java.util.Arrays.copyOf(Arrays.java:2219)at java.util.ArrayList.grow(ArrayList.java:242)at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)at java.util.ArrayList.add(ArrayList.java:440)at HeapOOM.main(HeapOOM.java:17)可以看到,在堆內存溢出時,除了會報錯java.lang.OutOfMemoryError外,還會跟著進一步提示Java heap space。
虛擬機棧和本地方法棧溢出
? ? ? 要讓虛擬機棧內存溢出,我們可以使用遞歸調用:因為每次方法調用都需要向棧中壓入調用信息,當棧的大小固定時,過深的遞歸將向棧中壓入過量信息,導致StackOverflowError:
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:10828 Exception in thread "main" java.lang.StackOverflowErrorat JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:10)at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)? ? ? 可以看到,在我的電腦上運行以上代碼,最多支持的棧深度是10828層,當發(fā)生棧溢出時,會報錯java.lang.StackOverflowError。
方法區(qū)溢出
? ? ? 方法區(qū)用于存放Class的相關信息,如類名、訪問修飾符、字段描述等,對于這個區(qū)域的測試,基本思路是運行時使用CGLib產生大量的類去填充方法區(qū),直到溢出:
public class JavaMethodAreaOOM {static class OOMObject {}public static void main(String[] args) {while( true) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OOMObject. class);enhancer.setUseCache( false);enhancer.setCallback( new MethodInterceptor() {@Overridepublic Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy) throws Throwable {return proxy.invokeSuper(obj, args);}});enhancer.create();}} }運行時增加虛擬機參數(shù):-XX:PermSize=10M -XX:MaxPermSize=10M,限制永久代大小為10M,最后報錯為java.lang.OutOfMemoryError: PermGen space。報錯信息明確說明,溢出區(qū)域為永久代。
總結 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? 本文主要說明Java虛擬機一共分為哪幾塊內存區(qū)域,以及這幾塊內存區(qū)域是否會內存溢出,如果這些區(qū)域發(fā)生內存溢出報錯如何。了解這些知識后,以后遇到內存溢出報錯,我們就可以定位到具體內存區(qū)域,然后具體問題,具體分析。
參考 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
- http://blog.csdn.net/ns_code/article/details/17565503
- 《深入理解Java虛擬機》
轉載于:https://www.cnblogs.com/timlearn/p/4025561.html
總結
以上是生活随笔為你收集整理的Java虚拟机——Java内存区域与内存溢出的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Midnight.js – 实现奇妙的固
- 下一篇: 对Java泛型的简单理解