JVM記憶體模型詳解

OkidoGreen發表於2019-05-26

 

JVM = 類載入器(classloader) + 執行引擎(execution engine) + 執行時資料區域(runtime data area)

 

執行時資料區域 

Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。這些區域都有各自的用途,以及建立和銷燬的時間,有的區域隨著虛擬機器程式的啟動而存在,有些區域則是依賴使用者執行緒的啟動和結束而建立和銷燬。

程式計數器(Program Counter Register)

  • 執行緒私有,它的生命週期與執行緒相同。
  • 可以看做是當前執行緒所執行的位元組碼的行號指示器。在虛擬機器的概念模型裡(僅是概念模型,各種虛擬機器可能會通過一些更高效的方式去實現),位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,如:分支、迴圈、跳轉、異常處理、執行緒恢復(多執行緒切換)等基礎功能。
  • 如果執行緒正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機器位元組碼指令的地址;如果正在執行的是Natvie方法,這個計數器值則為空(undefined)。
  • 程式計數器中儲存的資料所佔空間的大小不會隨程式的執行而發生改變,所以此區域不會出現OutOfMemoryError的情況。

Java虛擬機器棧(JVM Stacks)

  • 執行緒私有的,它的生命週期與執行緒相同。
  • 虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於儲存區域性變數表、操作棧、動態連結、方法出口等資訊。每一個方法被呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。
  • 區域性變數表存放了編譯期可知的各種基本資料型別(boolean、byte、char、short、int、float、long、double)、物件引用(reference型別),它不等同於物件本身,根據不同的虛擬機器實現,它可能是一個指向物件起始地址的引用指標,也可能指向一個代表物件的控制程式碼或者其他與此物件相關的位置)和returnAddress型別(指向了一條位元組碼指令的地址)。區域性變數表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的區域性變數空間是完全確定的,在方法執行期間不會改變區域性變數表的大小。
  • 該區域可能丟擲以下異常:
    • 當執行緒請求的棧深度超過最大值,會丟擲 StackOverflowError 異常;
    • 棧進行動態擴充套件時如果無法申請到足夠記憶體,會丟擲 OutOfMemoryError 異常。

本地方法棧(Native Method Stacks)

  • 與虛擬機器棧非常相似,其區別不過是虛擬機器棧為虛擬機器執行Java方法(也就是位元組碼)服務,而本地方法棧則是為虛擬機器使用到的Native 方法服務。虛擬機器規範中對本地方法棧中的方法使用的語言、使用方式與資料結構並沒有強制規定,因此具體的虛擬機器可以自由實現它。甚至有的虛擬機器(譬如Sun HotSpot 虛擬機器)直接就把本地方法棧和虛擬機器棧合二為一。
  • 與虛擬機器棧一樣,本地方法棧區域也會丟擲StackOverflowError和OutOfMemoryError異常。

Java堆(Heap)

  • 被所有執行緒共享,在虛擬機器啟動時建立,用來存放物件例項,幾乎所有的物件例項都在這裡分配記憶體。
  • 對於大多數應用來說,Java堆(Java Heap)是Java虛擬機器所管理的記憶體中最大的一塊。
  • Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱做“GC堆”。如果從記憶體回收的角度看,由於現在收集器基本都是採用的分代收集演算法,所以Java堆中還可以細分為:新生代和老年代;新生代又有Eden空間、From Survivor空間、To Survivor空間三部分。
  • Java 堆不需要連續記憶體,並且可以通過動態增加其記憶體,增加失敗會丟擲 OutOfMemoryError 異常。

方法區(Method Area)

  • 用於存放已被載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。
  • 和 Java 堆一樣不需要連續的記憶體,並且可以動態擴充套件,動態擴充套件失敗一樣會丟擲 OutOfMemoryError 異常。
  • 對這塊區域進行垃圾回收的主要目標是對常量池的回收和對類的解除安裝,但是一般比較難實現,HotSpot 虛擬機器把它當成永久代(Permanent Generation)來進行垃圾回收。
  • 方法區邏輯上屬於堆的一部分,但是為了與堆進行區分,通常又叫“非堆”。


執行時常量池(Runtime Constant Pool)

  • 執行時常量池是方法區的一部分。
  • Class 檔案中的常量池(編譯器生成的各種字面量和符號引用)會在類載入後被放入這個區域。
  • 除了在編譯期生成的常量,還允許動態生成,例如 String 類的 intern()。這部分常量也會被放入執行時常量池。

注:

  • 在 JDK1.7之前,HotSpot 使用永久代實現方法區;HotSpot 使用 GC 分代實現方法區帶來了很大便利;
  • 從 JDK1.7 開始HotSpot 開始移除永久代。其中符號引用(Symbols)被移動到 Native Heap中,字串常量和類引用被移動到 Java Heap中。
  • 在 JDK1.8 中,永久代已完全被元空間(Meatspace)所取代。元空間的本質和永久代類似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機器中,而是使用本地記憶體。因此,預設情況下,元空間的大小僅受本地記憶體限制。

​​​​​​​直接記憶體(Direct Memory)

  • 直接記憶體(Direct Memory)並不是虛擬機器執行時資料區的一部分,也不是Java虛擬機器規範中定義的記憶體區域,但是這部分記憶體也被頻繁地使用,而且也可能導致OutOfMemoryError 異常出現。
  • 在 JDK 1.4 中新加入了 NIO 類,引入了一種基於通道(Channel)與緩衝區(Buffer)的 I/O方式,它可以使用 Native 函式庫直接分配堆外記憶體,然後通過一個儲存在 Java 堆裡的 DirectByteBuffer 物件作為這塊記憶體的引用進行操作。這樣能在一些場景中顯著提高效能,因為避免了在Java 堆和 Native 堆中來回複製資料。

相關文章