標籤:java 虛擬機器 記憶體溢出 jvm
每個Java虛擬機器都有一個類載入器子系統,根據某個全限定名來裝入類型,同樣每個Java虛擬機器都有一個執行引擎,它負責執行那些包含在被裝載類的方法中的指令。
當虛擬機器運行一個程式時,就需要從已載入的檔案中得到資訊,將這些資訊組織到運行時資料區,以便於管理。
Java運行時的資料區域劃分
1、程式計數器:程式計數器是一塊較小的記憶體空間,可以看做是當前線程的位元組碼的行號指標。
Java虛擬機器的多線程是通過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個時刻,一個處理器只會執行一條線程中的指令。因此為了線程切換後能恢複到正確的執行位置,每一個線程都需要有一個獨立的程式計數器,各條線程之間互不影響,隔離儲存區 (Isolated Storage)。所以也稱該記憶體地區為線程私人的記憶體。
此記憶體地區是唯一一個在Java虛擬機器規範中沒有規定任何OOM(OutOfMemoryError)情況的地區。
2、Java虛擬機器棧
其生命週期與線程相同,每個方法在執行的同時會建立一個棧幀用於儲存局部變數表、運算元棧、動態連結等。每一個方法調用到完成的過程,對應著棧幀入棧到出棧的過程。
人們經常說的java記憶體分為堆記憶體和棧記憶體,所謂的棧記憶體就是指Java虛擬機器棧,或者說是虛擬機器棧中局部變數表的部分。
局部變數表中存放各種基本類型和對象引用。
若線程請求的棧的深度大於虛擬機器所允許的深度,將拋出StackOverFlowError
如果虛擬機器的棧可以動態擴充,但擴充也有一個上限,擴充時無法申請到足夠的記憶體,拋出OutOfMemoryError
3、本地方法棧
本地方法棧的作用類似於虛擬機器棧。
區別在於Java虛擬機器棧為虛擬機器執行Java方法服務
而本地方法棧則為虛擬機器使用到的Native方法服務
類似虛擬機器棧,本地方法棧也會拋出StackOverFlowError和OutOfMemoryError異常
4、Java堆
Java堆是Java虛擬機器所管理的記憶體中最大的一塊,是線程共用的地區。
Java堆由新生代、老年代和永久代組成
新生代:
是類的成長、誕生、消亡的地區,最後被垃圾收集器收集,結束生命。新生代又分為Eden、From survivor和 To survivor。對象首先是分配在Eden,當Eden中不夠儲存時,會將其存到survivor,如果survivor中無法儲存會移動到老年代。
老年代:
存放從新生代中篩選出來的對象。
JAVA堆可以處於物理上的不連續空間,只要邏輯上是連續的即可。
堆在實現時,可以是固定大小、也可以動態擴充(-Xmx和-Xms)。當堆無法在擴充時,會拋出OOM異常。
5、方法區(永久代):
JAVA虛擬機器規範把方法區描述為堆的一個邏輯部分,但是它還有另外一別名是非堆(Non-Heap),目的應該與Java堆區分開來。
是一個常駐記憶體地區,儲存了每一個類的結構資訊,例如運行時常量
池(Runtime Constant Pool)、欄位和方法資料、建構函式和普通方法的位元組碼內容、還包
括一些在類、執行個體、介面初始化時用到的特殊方法,也就是運行環境必須的類資訊,此地區的是不會被垃圾收集器回收掉的。JVM關掉時,才會釋放此地區所佔用的記憶體。方法區無法滿足記憶體配置需求時,將拋出OOM。
Ps:方法區和永久代本質上不等價,但是對HotSpot來說,使用永久代來實現方法區,對於其他虛擬機器(BEA JRockit、IBM J9等)是不存在永久代的概念的。
6、運行時常量池
運行時常量池是方法區的一部分,用於存放編譯時間期產生的各種字面量和符號引用。運行期間也可以將新的常量放入池中,比如String類的intern()方法。當常量池無法再申請到記憶體時會拋出OOM異常。
7、直接記憶體
直接記憶體是java虛擬機器記憶體管理之外的一個記憶體地區,java引入NIO後,引入了一種基於通道與緩衝區的I/O方式,可以使用Native函數庫直接分配堆外記憶體,然後通過堆內對象對這塊記憶體進行操作。
顯然,本機直接記憶體的分配不會受到JAVA堆大小的限制,這類記憶體也會產生OOM異常,因為原生記憶體大小是有限的。
JAVA堆溢出
JAVA堆用於儲存物件執行個體,不斷建立對象,超過堆的大小,即可造成堆溢出。
/** * 參數:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError * -Xms20m 堆的最小值 * -Xmx20m 堆的最大值 * 設定為一樣,可避免自動擴充 * * -XX:+HeapDumpOnOutOfMemoryError * 讓虛擬機器在出現記憶體溢出的時候DUMp出當前記憶體堆轉儲快照以便事後分析 * * */public class HeapOOM { public static void main(String[] args) { List<HeapOOM> list = new ArrayList<HeapOOM>(); while(true) { list.add(new HeapOOM()); } }}//輸出: Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
虛擬機器棧和本地方法棧溢出
在單線程的情況,只能形成的異常是StackOverflowError,我們使用遞迴造成棧溢出。
/** *使用 -Xss128k 減小棧容量 */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; } }}//輸出:Exception in thread "main" java.lang.StackOverflowError
周志明老師在書上說到,在多線程情況下,會造成OutOfMemoryError異常,可是我試了一下,結果是死機了,最後Eclipse沒反應,等了好久,強制關機了。
我貼上代碼,但是運行時可能會死機,大家還是小心行事。
public class JavaVMStackSOF { private void dontStop() { while(true) {} } public void stackLeakByThread() { while(true) { Thread th = new Thread(new Runnable() { @Override public void run() { dontStop(); } } ); th.start(); } } public static void main(String[] args) { JavaVMStackSOF oom = new JavaVMStackSOF(); oom.stackLeakByThread(); }}
方法區溢出
/* * VM args : -XX:PermSize=10M -XX:MaxPermSize=10M 限制方法區的大小 * * * Exception in thread "main" java.lang.OutOfMemoryError: PermGen space at java.lang.String.intern(Native Method) * */ public class RuntimeConstantPoolOOM1 { public static void main(String[] args) { //使用List保持引用,避免FullGC回收常量池 List<String> list = new ArrayList<String>(); int i = 0; while(true) { list.add(String.valueOf(i++).intern()); System.out.println(i); } } }//輸出:Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
關於書上String.intern的例子,我從網上轉載了一篇很詳細的文章解釋
深入解析String#intern
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
Java虛擬機器結構及常見記憶體溢出異常