The runtime data area model of JVM

CLYH發表於2020-10-14

瞭解執行時資料區模型是認識Java虛擬機器的重要前提。雖然說JVM甚至說較為常用的HotSpot虛擬機器本身就有自動分配和管理記憶體的功能,但是學習JVM是為了能夠在出錯的時候甚至在優化的時候能夠由開發人員自己去處理,虛擬機器提供的自動記憶體管理只能在合適或通常情況下是沒問題的。

執行時資料區又稱為Java虛擬機器執行時資料區,它的大體模型結構如下圖所示

執行時資料區其實就是一塊記憶體區域,按照不同的型別和功能進行劃分。主要有程式計數器,虛擬機器棧,本地方法棧,方法區和堆。

按照執行緒的使用情況可以分為執行緒私有和執行緒共享

其中方法區和堆屬於執行緒共享,而虛擬機器棧,本地方法棧和程式計數器屬於執行緒私有

程式計數器

程式計數器用於記錄Java程式執行時的位元組碼行號等相關資訊,線上程進行上下文切換時,迴圈,異常處理等進行計算和相關的記錄。如果程式執行的是本地方法,那麼程式計數器記錄的值應該是空的,僅有執行Java方法時才會有相應的位元組碼行號。每一個執行緒都持有一個程式計數器,並獨立儲存,是執行緒私有。程式計數器在記憶體分配中佔有較小的內容,主要的工作就是載入程式的執行。

虛擬機器棧和本地方法棧

虛擬機器棧和本地方法棧都是一塊管理方法執行的記憶體區域。並且也屬於執行緒私有,生命週期和執行緒相同。

虛擬機器棧管理的是Java方法的執行,每一次呼叫方法的時候,會通過棧幀進行相關資訊的儲存,然後棧幀入棧操作,呼叫結束後就會進行出棧。棧幀中主要儲存的是區域性變數表、運算元棧、動態連線、方法出口等資訊。

區域性變數表中存放了編譯期可知的各種Java虛擬機器基本資料型別、物件引用和returnAddress型別(指向一條位元組碼指令的地址)

資料型別在區域性變數表中是以區域性變數槽來表示,並且在編譯期間完成分配,方法執行期間是不會變更的。關鍵的是,long和double型別會佔用兩個變數槽,而其他型別只佔用一個。如果執行緒請求的棧的深度大於虛擬機器鎖執行的深度就會丟擲StackOverflowError異常;如果Java虛擬機器棧允許動態擴容,當無法申請到足夠的記憶體時則會丟擲OutOfMemoryError異常。

本地方法棧和虛擬機器棧基本相同,主要區別就是本地方法棧管理的是本地方法的執行。

Java堆

 

Java堆在JVM啟動的時候就建立了,是記憶體管理中最大的一塊區域,主要用於存放物件例項(包括陣列),是執行緒共享的。

Java堆也是垃圾收集器管理的記憶體區域,因此Java堆也被稱為GC堆。

從垃圾回收的角度上看,JVM會對不同物件例項的使用情況進行生命週期的管理,例如新生代,老年代的概念。

從記憶體分配的角度上看,除了一大部分用於存放物件,堆還有提供一小部分用於執行緒私有的分配緩衝區。這個分配快取區主要是為了提高物件分配的效率。

方法區

方法區主要用於儲存被虛擬機器載入的的型別資訊、常量、靜態變數、即時編譯器編譯後的程式碼快取等資料。方法區也同樣是執行緒共享的。

方法區也可以是垃圾回收器的管理物件。但主要是對執行時常量池的回收和型別的解除安裝。

方法區中有一個重要的部分就是執行時常量池。執行時常量池主要用於儲存常量表。在Class檔案中處理有類的版本、欄位、方法、介面等描述資訊外,還有常量池表。常量池表用於存放編譯器生成的各種字面量和符號引用,這部分記憶體在被類載入器載入後存放到方法區的常量池中。

執行時常量池相對於Class檔案常量池的另外一個重要特徵就是具備動態性。也就是說常量池中的內容並不一定只能通過編譯期間進行儲存,在程式執行期間也可以。典型的例子就行String類中的intern()方法。

intern()方法呼叫時會先檢查執行時常量池中是否已存在該常量,如果存在則直接返回該引用,否則將新的值儲存執行時常量池,並返回該引用。

以下例子演示了執行時常量池的動態性。

public class RuntimeDemo {

    public static void main(String[] args) {
        String a = "a";

        String a1 = new String("a");

        String a2 = new String ("a");

        String a3 = new String("b");

        /** a1是物件引用 */
        System.out.println(a==a1);

        /** intern()先檢查常量池中是否存在“a“的字面量常量,存在則將a2執行此常量,否則建立新的物件引用 */
        System.out.println(a==a2.intern());

        System.out.println(a==a3);

    }

}

執行結果

false
true
false

直接記憶體

直接記憶體並不屬於JVM中記憶體管理中的一部分。

如果你瞭解NIO的相關知識,那麼你會更容易理解直接記憶體的意義。NIO的基本介紹可以看下這裡必須瞭解的三種IO操作--BIO、NIO、AIO

直接記憶體可以通過本地Native方法分配到主機的記憶體,這一塊記憶體會受到主機記憶體限制的直接影響,在NIO中,就有通過記憶體對映的方式提高程式的IO效率,實現原理就是通過Java本地緩衝區來對映主機實體記憶體的引用從而不需要進行從Java記憶體複製到實體記憶體的操作,提高了IO效率。

 

 

相關文章