天天看點

jvm-運作時記憶體結構

記憶體模型示意圖

jvm-運作時記憶體結構

每個區域的作用簡述

pc寄存器 (program counter)

每一條java虛拟機線程都有自己的pc寄存器

在任意時刻,一條java虛拟機線程隻會執行一個方法的代碼,正在被線程執行的方法稱為該線程的目前方法

(如果這個方法不是native的,那pc寄存器就儲存java虛拟機正在執行的位元組碼指令的位址)

(如果這個方法是natice的,那pc寄存器的值是undefined)

pc寄存器的容量至少應當能儲存一個returnAddress類型的資料或者一個與平台相關的本地指針的值

虛拟機棧 (virtual machine stack)

每條java虛拟機線程都有自己私有的java虛拟機棧,這個棧與線程同時建立,用于存儲棧幀 (用于存儲局部變量與一些尚未算好的結果)

除了棧幀的入棧和出棧之外,不會再受其他因素影響,是以棧幀可以在堆中配置設定,java虛拟機棧所使用的記憶體不需要保證是連續的

java虛拟機規範允許java虛拟機棧被實作成固定大小的,也允許根據計算來動态擴充和收縮

(如果是固定大小的,那每一個線程的java虛拟機棧的容量可以線上程建立時獨立標明)

如果線程請求配置設定的棧容量超過java虛拟機棧允許的最大容量,java虛拟機将抛出一個StackOverflowError異常

如果java虛拟機棧可以動态擴充,并且在嘗試擴充時無法申請到足夠記憶體,或者在建立新線程的時候沒有足夠的記憶體去建立對應的虛拟機棧,那麼java虛拟機将會抛出一個OutOfMemoryError異常

堆 (heap)

堆是可供各個線程共享的運作時區域,也是提供所有類執行個體和數組對象配置設定記憶體的區域

堆在java虛拟機啟動的時候被建立,它存儲了被自動記憶體管理系統(垃圾收集器)所管理的各種對象,這些受管理的對象無需也無法顯示地銷毀

堆的用量可以是固定的,也可以是随程式執行的需求動态擴充,并在不需要過多空間的時候自動收縮

堆所使用的記憶體空間不需要保證是連續的

如果實際所需的堆超過了自動記憶體管理系統能提供的最大容量,那java虛拟機将會抛出一個OutOfMemoryError異常

方法區 (method area)

是堆的邏輯組成部分

可供各個線程共享的運作時區域

存儲了每個類的結構資訊

在虛拟機啟動的時候建立

可以選擇不實作垃圾收集和壓縮

用量可以是固定的,也可以是随程式執行的需求動态擴充,并在不需要過多空間的時候自動收縮

所使用的記憶體空間不需要保證是連續的

如果方法區的記憶體空間不能滿足記憶體配置設定請求,那java虛拟機将會抛出一個OutOfMemoryError異常

運作時常量 (runtime constant pool)

它是class檔案中每一個類或者接口的的常量池表的運作時表示形式,包括了多種不同的常量,從編譯期可知的數值字面量到必須在運作期解析後才能獲得的方法和引用

每一個運作時常量都在java虛拟機的方法區中配置設定,在加載類和接口到虛拟機後,就建立對應的運作時常量池

建立類或者接口時,如果構造運作時常量池所需要的記憶體空間超過了方法區所能提供的最大值,那java虛拟機将會抛出一個OutOfMemoryError異常

本地方法棧 (native method stack)

Java虛拟機可能會用到傳統的棧(稱為C stack)來支援native方法的執行,這個棧就是本地方法棧

如果java虛拟機支援本地方法棧,那麼每一個線程的本地方法棧容量可以在建立棧的時候獨立標明

如果線程請求配置設定的棧容量超過本地方法棧的最大容量,java虛拟機将抛出一個StackOverflowError異常

如果本地方法棧可以動态擴充,并且在嘗試擴充時無法申請到足夠記憶體,或者建立新線程時沒有足夠的記憶體區建立對應的本地方法棧,那java虛拟機将會抛出一個OutOfMemoryError異常

棧幀 (frame)

棧幀的存儲空間由建立它的線程配置設定在java虛拟機棧中

棧幀是用來存儲資料和部分過程結果的資料結構,同時也用來處理動态連結,方法傳回值,異常分派

棧幀随着方法的調用而建立,随着方法的結束而銷毀,無論方法是正常完成還是異常完成,都算作方法結束

每個棧幀都有自己的本地變量表,操作數棧,指向目前方法所屬的類的運作時常量池的引用

在某條線程執行過程中的某個時間點上,隻有目前正在執行的那個方法的棧幀是活動的

調用新的方法時,新的棧幀會随之而建立,并且會随着程式控制權移交到新方法,而稱為新的目前棧幀

方法傳回之際,目前棧幀會傳回此方法的執行結果給前一個棧幀,然後虛拟機會丢棄掉目前棧幀,使得前一個棧幀重新稱為目前棧幀

注意 : 棧幀是線程本地私有的資料,不可能在一個棧幀之中引用另外一個線程的棧幀

棧幀 - 局部變量表

每個棧幀内部都包含一組稱為局部變量表的變量清單

棧幀中的局部變量表的長度由編譯期決定

存儲于類或接口的二進制表示之中(class),即通過方法的code屬性儲存以及提供給棧幀使用

一個局部變量可以儲存一個類型為如下的資料,boolean,byte,char,short,int,float,reference,returnAddress

兩個連續的局部變量可以儲存一個類型為long或double的資料

局部變量表使用索引來進行定位通路

java虛拟機使用局部變量表來完成方法調用時的參數傳遞

棧幀 - 操作數棧

每個棧幀内部都包含一個稱謂操作數棧的後進先出棧

棧幀中的操作數棧的最大深度由編譯期決定,并且通過方法的code屬性儲存以及提供給棧幀使用

棧幀在剛剛建立時,操作數棧是空的

java虛拟機提供一些位元組碼指令來從局部變量表或者對象執行個體的字段中複制常量或者變量的值到操作數棧中,也提供了一些指令用于操作數棧,取走資料,操作資料,以及把操作結果重新入棧

在調用方法時,操作數棧也用來準備調用方法的參數以及接收方法的傳回結果

操作數棧的每一個位置上可以儲存一個java虛拟機定義的任意資料類型的值,包括long和double

任意時刻,操作數棧都會有一個确定的深度,一個long或者double類型的資料會占用兩個機關的棧深度,其他資料類型則會占用一個機關的棧深度

棧幀 - 動态連結

每個棧幀内部都包含一個指向目前方法所在類型的運作時常量池的引用,以便對目前方法實作動态連結

在class檔案裡,一個方法若要調用其他方法,或者通路成員變量,則需要通過符号引用來表示,動态連結的作用就是将這些符号引用所表示的方法轉換為對實際方法的直接引用

溢出的執行個體代碼

棧溢出

public class StackOom { public int num = 1; public void stack() { num++; this.stack(); } public static void main(String[] arge) { StackOom stackOom = new StackOom(); stackOom.stack(); }}      
Exception in thread "main" java.lang.StackOverflowError at org.itkk.learn.StackOom.stack(StackOom.java:15) at org.itkk.learn.StackOom.stack(StackOom.java:15) at org.itkk.learn.StackOom.stack(StackOom.java:15) at org.itkk.learn.StackOom.stack(StackOom.java:15)      

以上這段代碼,stack方法不斷的遞歸調用,最終達到java虛拟機棧的最大容量,就抛出了StackOverflowError異常

堆溢出

public class HeapOom { private List<byte[]> data = new ArrayList<>(); public void heap() { while (true) { data.add(new byte[1024 * 1024]); } } public static void main(String[] arge) { HeapOom heapOom = new HeapOom(); heapOom.heap(); }}      
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at org.itkk.learn.HeapOom.heap(HeapOom.java:21) at org.itkk.learn.HeapOom.main(HeapOom.java:32)      

以上這段代碼,heap方法,以死循環的方式不斷的往data中添加1M大小的數組對象,最終堆中的記憶體空間不能滿足data存放的要求,而抛出了OutOfMemoryError異常

元空間溢出

public class MetaspaceOom { static String str = "string"; public static void main(String[] arge) { List<String> list = new ArrayList<>(); while (true) { str += str; list.add(str.intern()); } }}      
java.lang.OutOfMemoryError: Metaspace at sun.misc.Launcher.<init>(Unknown Source) at sun.misc.Launcher.<clinit>(Unknown Source) at java.lang.ClassLoader.initSystemClassLoader(Unknown Source) at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)      

以上這段代碼使用如下指令執行 :

java -XX:MetaspaceSize=2m -XX:MaxMetaspaceSize=2m org.itkk.learn.MetaspaceOom      

在運作MetaspaceOom類時,限定了元空間的大小,而main方法中,則以死循環的方式不停的拷貝生成新的字元串常量(intern方法),而字元串常量存儲于元空間中,最終字元串常量超出了元空間的容量,進而抛出OutOfMemoryError異常(Metaspace)

java8中永久代的變化

在java8中,永久代已經由元空間替代了,在上面章節中的元空間溢出的執行個體代碼中,在jdk6中,會出現"PermGen Space"溢出,在jdk7和jdk8中,則是出現"Java heap space"溢出

而且在java8中,-XX:PermSize和-XX:MaxPermGen已經提示無效了,如下:

使用如下指令運作java -XX:PermSize=8m -XX:MaxPermSize=8m -Xmx16m org.itkk.learn.MetaspaceOomD:developJetBrainsIdeaProjectslearnleanmain	argetclasses>java -XX:PermSize=8m -XX:MaxPermSize=8m -Xmx16m org.itkk.learn.MetaspaceOomJava HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=8m; support was removed in 8.0Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=8m; support was removed in 8.0      

會得出如下兩個警告,可以得知,Perm在jdk8中已經不存在了

元空間和永久代最大的差別在于,元空間不再java虛拟機記憶體中,而是直接使用的本地記憶體,空間大小僅受本地記憶體的限制,并且可以使用jvm參數來指定元空間的大小

上面元空間溢出的執行個體代碼中就使用了-XX:MetaspaceSize和-XX:MaxMetaspaceSize來指定元空間的最大和最容量