JVM內存模型/內存空間:運行時數據區

JVM內存模型/內存空間

Java虛擬機JVM運行起來,就會給內存劃分空間,這塊空間成為運行時數據區。

運行時數據區主要劃分為以下 6個 :

image-20210829150433288

① 程序計數器 (Program Counter Register)

  • 一塊較小的內存空間,可以看作是當前線程所執行的字節碼的行號指示器
  • 線程私有的內存
  • 值得註意的是:《Java虛擬機規范》中,唯一一個沒有規定任何OutOfMemoryError情況的區域!!!

程序計數器也可以稱為PC寄存器,通俗的講就是指令緩存,它主要用來緩存當前程序執行的下一條指令的地址,CPU根據這個地址找到將要執行的指令。這個寄存器是JVM內部實現的,不是物理概念上的計數器,不過和JVM的實現邏輯一樣。

② Java虛擬機棧 (VM Stack) 

  • Java方法執行的線程內存模型
  • 每一個線程運行起來的都會對應一個棧(線程棧),棧中的數據是該線程獨有的,不會產生資源共享的情況,因此線程棧是線程安全的。
  • 棧當中存放的是棧幀 
    • 每個Java方法的執行對應著一個棧幀的進棧和出棧的操作
    • 當線程調用方法時,就形成一個棧幀,並將這個棧幀進行壓棧操作,方法執行完之後進行出棧操作。
    • 這個棧幀中包括:局部變量、操作數棧、指向當前方法對應類的常量池引用、方法返回地址等信息
  • 為虛擬機執行Java方法(也就是字節碼)服務
  • 線程私有的內存
  • 其生命周期與線程相同
  • 兩類異常:
    • 如果線程請求的棧深度大於虛擬機所允許的深度,將拋出StackOverflowError異常
    • 如果JVM棧容量可以動態擴展,當棧擴展時無法申請到足夠的內存時,會拋出OutOfMemoryError異常

③ 本地方法棧 (Native Method Stack)

區別於 “Java虛擬機棧” 

  • 本地方法棧隻為虛擬機使用到的本地(Native)方法服務,為其運行提供內存環境 
    • 本地方法是指JVM需要調用非Java語言所實現的方法,例如C/C++/C# 
  • JVM棧運行的是Java方法

在JVM規范中,沒有強化性要求實現方一定要劃分出本地方法棧(例如:HotSpot虛擬機將本地方法棧和棧合二為一)和具體實現(不同的操作系統,對JVM規范的具體實現都不一樣)。

  • 同 “Java虛擬機棧” 一樣,本地方法棧也有兩類異常: 
    • 棧深度溢出時,將拋出StackOverflowError異常
    • 棧擴展失敗時,會拋出OutOfMemoryError異常

④ Java堆 (Java Heap) 

  • 虛擬機所管理的內存中最大的一塊
  • Java堆是被所有線程共享的一塊內存區域
  • 唯一的目的:存放對象示例。
    • Java中 “幾乎” 所有的對象實例都在這裡分配內存;
    • 但是,由於現在技術發展,說 “Java對象示例都分配在堆上” 也漸漸變得不是那麼絕對瞭。
  •  Java堆是垃圾收集器管理的內存區域,也稱“GC堆”。 
    • 堆內存中的對象沒有被引用,會自動被Java的垃圾回收機制回收。
  • 當在方法中定義瞭局部變量:
    • 如果局部變量是基本數據類型,直接存放在棧內存中;
    • 如果局部變量是引用數據類型,會將變量值存放在堆內存中,棧內存中隻存放引用地址。
  • Java堆可以處於物理上不連續的內存空間,但在邏輯上它應該是被視為連續的。
  • 如果在Java堆中沒有內存完成實例分配,並且Java堆也無法再擴展時,Java虛擬機將會拋出OutOfMemoryError異常

 ⑤ 方法區(Method Area)

  • 和 “Java堆” 一樣,是被所有線程共享的一塊區域。
  • 主要存放每一個被加載的class的信息

class信息主要包含魔數(確定是否是一個class文件),常量池,訪問標志(當前的類是普通類還是接口,是否是抽象類,是否被public修飾,是否使用瞭final修飾等描述信息…),字段表集合信息(使用什麼訪問修飾符,是實例變量還是靜態變量,是否使用瞭final修飾等描述信息…),方法表集合信息(使用什麼訪問修飾符,是否靜態方法,是否使用瞭 final 修飾,是否使用瞭synchronized修飾,是否是native方法…)等內容。

當一個類加載器加載瞭一個類的時候,會根據這個class文件創建一個class對象,class對象就包含瞭上述的信息。後續要創建這個類的實例,都根據這個class對象創建出來的。

在《Java虛擬機規范》中,把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫作 “非堆” ,目的是與Java堆區分開來。

如果方法區無法滿足新的內存分配需求時,將拋出OutOfMemoryError異常

⑥ 運行時常量池 (Running Constant Pool)

  • 運行時常量池是方法區的一部分。
  • 存放class中最重要的資源,JVM為每一個class對象都維護著一個常量池。
  • 常量池表:用於存放編譯期生成的各種字面量與字符引用。
  • 這部分內容將在類加載後存放到方法區的運行時常量池中。 
  • 運行時常量池相對Class文件常量池的一個重要特征是具備動態性。
  • 當常量池無法再申請到內存時,會拋出OutOfMemoryError異常

【特】 直接內存

運行時數據區主要為以上6個區域,但是JVM所管理的還有一個較特殊的區域:

  • 直接內存 (Direct Memory)
  • 既不是虛擬機運行時數據區的一部分,也不是《Java虛擬機規范》中定義的內存區域。
  • 但是這部分內存區域也被頻繁地使用,而且也可能導致OutOfMemoryError異常出現

1.在JDK 1.4中新加入瞭NIO(New Input/Output)類,引入瞭一種基於通道(Channel)與緩沖區(Buffer)的I/O方式,它可以使用Native函數庫 直接分配堆外內存,然後通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免瞭在Java堆和Native堆中來回復制數據。
2.在本機直接內存的分配不會受到Java堆大小的限制,但是,既然是內存,則肯定還是會受到本機總內存(包括RAM及SWAP區或者分頁文件)的大小及處理器尋址空間的限制。服務器管理員配置虛擬機參數時,一般會根據實際內存設置-Xmx等參數信息,但經常會忽略掉直接內存,使得各個內存區域的總和大於物理內存限制(包括物理上的和操作系統級的限制),從而導致動態擴展時出現OutOfMemoryError異常。

總結

本片文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: