jvm - 運行時內存結構

jvm - 運行時內存結構

注意 : 本系列文章爲學習系列,部份內容會取自相關書籍或者網絡資源,在文章末尾處會有標註java

內存模型示意圖

Jvm運行時內存結構

每一個區域的做用簡述

pc寄存器 (program counter)

每一條java虛擬機線程都有本身的pc寄存器git

在任意時刻,一條java虛擬機線程只會執行一個方法的代碼,正在被線程執行的方法稱爲該線程的當前方法程序員

(若是這個方法不是native的,那pc寄存器就保存java虛擬機正在執行的字節碼指令的地址)數組

(若是這個方法是natice的,那pc寄存器的值是undefined)網絡

pc寄存器的容量至少應當能保存一個returnAddress類型的數據或者一個與平臺相關的本地指針的值數據結構

虛擬機棧 (virtual machine stack)

每條java虛擬機線程都有本身私有的java虛擬機棧,這個棧與線程同時建立,用於存儲棧幀 (用於存儲局部變量與一些還沒有算好的結果)框架

除了棧幀的入棧和出棧以外,不會再受其餘因素影響,因此棧幀能夠在堆中分配,java虛擬機棧所使用的內存不須要保證是連續的jvm

java虛擬機規範容許java虛擬機棧被實現成固定大小的,也容許根據計算來動態擴展和收縮學習

(若是是固定大小的,那每個線程的java虛擬機棧的容量能夠在線程建立時獨立選定)this

若是線程請求分配的棧容量超過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.MetaspaceOom

D:\develop\JetBrains\IdeaProjects\learn\leanmain\target\classes>java -XX:PermSize=8m -XX:MaxPermSize=8m -Xmx16m  org.itkk.learn.MetaspaceOom
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=8m; support was removed in 8.0
Java 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來指定元空間的最大和最容量

參考文獻

文章中部份內容取自<<Java虛擬機規範.Java SE 8版>>

結束

俗話說,不瞭解jvm的java程序員,等於耍流氓

深刻的瞭解底層的能有助於咱們更好的理解java程序運行的機制,從而幫助咱們寫出更好的代碼.

關於本文內容 , 歡迎你們的意見跟建議

代碼倉庫 (博客配套代碼)


想得到最快更新,請關注公衆號

想得到最快更新,請關注公衆號

相關文章
相關標籤/搜索