1. 程式人生 > 其它 >深入理解JVM - 執行時資料區域

深入理解JVM - 執行時資料區域

1. 執行時資料區域

注意JVM執行時資料區域與Java記憶體模型的區別

  • JVM 執行時資料區域:JVM 所管理的記憶體劃分
  • Java記憶體模型:遮蔽底層硬體和作業系統的區別,在語言級抽象java的記憶體訪問,使得在不同的環境中java一致的記憶體訪問效果

2. 程式計數器 Program Counter Register

當前執行緒所執行的位元組碼的行號指示器
JVM概念模型中,位元組碼直譯器通過改變PC選取下一條要執行的指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個指示器完成。
執行緒為OS排程的基本單位,Java的執行緒模型使用的是核心指稱的一對一模型,每個執行緒都需要有自己的PC,因此PC是執行緒私有

的記憶體。
若當前執行緒正在執行Java方法,則PC為正在執行的虛擬機器位元組碼指令地址;若正在執行本地方法(Native),則值未定義(Undefined)。
唯一一個未在《JVM規範》中指定OutOfMemoryError的記憶體區域

JVM概念模型:有點類似Java記憶體模型,都是高度的抽象,JVM概念模型則是JVM的抽象,而具體實現不一定根據這個抽象模型實現,但是具有相同的效果/功能。

3. Java 虛擬機器棧 Java Virtual Machine Stack

執行緒私有,生命週期與執行緒相同
虛擬機器棧描述Java方法執行的執行緒記憶體模型:一個方法的執行和退出對應一個棧幀(Stack Frame)的入棧和出棧。
棧幀

存放著區域性變量表運算元棧動態連結方法出口等資訊。
通常C/C++中描述的堆記憶體、棧記憶體中的棧記憶體就是這裡的虛擬機器棧,或者是虛擬機器棧中的區域性變量表部分。

區域性變量表
存放了編譯期可知的各種Java虛擬機器基本資料型別(8種)、物件引用(物件指標或控制代碼指標)和returnAddress(指向一條位元組碼指令的地址)。
這些資料在區域性變量表的儲存空間以區域性變數槽(Slot)表示,64位的long和double佔用兩個變數槽,其餘資料型別佔用兩個。
區域性變量表所需記憶體空間在編譯期完成分配,當進入一個方法時,需要在棧幀中分配多大的區域性變數空間是完全確定的。
虛擬機器具體使用多大的記憶體(32、64位?)實現一個slot取決於具體的實現。

棧幀
棧幀中的區域性變量表大小編譯期確定,那麼運算元棧棧是否能完全確定?棧幀需要分配的具體大小是不能確定的嗎?按照位元組碼看,運算元棧需要的空間大小應該也是能在編譯期確定的呀?

異常
執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲 StackOverflowError
如果Java虛擬機器棧容量可以動態擴充套件(取決於具體VM實現,HotSpot不能),當棧擴充套件時無法申請到足夠記憶體會丟擲 OutOfMemoryError
即便HotSpot的虛擬機器棧無法動態擴充套件,但是當申請一個執行緒的棧記憶體時就沒有沒有足夠記憶體也會丟擲 OutOfMemoryError

4. 本地方法棧 Native Stack

和虛擬機器棧的作用基本相同,為方法服務,不過本地方法棧為本地(Native)方法服務。
《JVM》為對此記憶體的實現有限制,HotSpot 將虛擬機器棧和本地方法棧合二為一實現。
異常
同虛擬機器棧,在棧深度溢位和棧擴充套件失敗時分別丟擲 StackOverflowErrorOutOfMemoryError

5. Java 堆 Java Heap

虛VM所管理記憶體最大的一塊,執行緒共享,VM啟動時建立,唯一目的是建立物件例項,幾乎所有例項物件都在這裡分配
即時編譯技術的進步,尤其逃逸分析技術的日漸強大,棧上分配、標量替換等優化手段,導致物件可能不在堆中分配。優化後類似C#的struct?
物理上課不連續,但邏輯上必須連續。
課實現為可擴充套件的、也可實現為固定大小的,主流VM均實現為可擴充套件的。

異常
若在Java堆中無法完成例項分配,且堆也無法擴充套件時,VM將丟擲 OutOfMemoryError

6. 方法區 Method Area

執行緒共享、儲存已被VM分配的型別資訊、常量、靜態變數、即時編譯器編譯後的程式碼快取等資料
永久代 Permanent Generation
方法區在JDK8前的常用稱呼,主要是在之前 HotSpot 使用永久代實現方法區,將垃圾收集器的分代設計擴充套件至方法區,省去單獨的記憶體管理設計。其他虛擬機器沒有這個稱呼。
JDK7 HotSpot將原永久代的字串常量、靜態變數移出。
JDK8 Hotspot 廢棄永久代,使用元空間(metaspace)實現,將JDK7中永久代剩餘內容(主要是型別資訊)移到元空間。

《JVM規範》對方法區限制很寬鬆,不需要連續實體記憶體、可選擇固定大小或可擴充套件、可不實現垃圾收集。
這部分記憶體回收主要針對常量和型別的解除安裝。

異常
當方法區無法滿足新的記憶體分配需求,將丟擲 OutOfMemoryError

7. 執行時常量池 Runtime Constabnt Pool

方法區一部分。
class檔案中除了類版本、欄位、方法、介面等描述資訊,還有常量池表(Constant Pool Table),存放編譯期生成的各種字面量與符號引用,這部分在類載入後放到方法區的執行時常量池中
一般來說,除了儲存class檔案中的符號引用,也會將由符號引用翻譯出來的直接引用也儲存在執行時常量池中。

執行時常量池和class常量池的一個區別是動態性,不要求一定是編譯時產生,也可在執行時放入,如 String#intern。

異常
方法區一部分,當常量池無法申請到足夠記憶體丟擲 OutOfMemoryError

8. 直接記憶體 Direct Memory

非執行時資料區域一部分,非《JVM規範》定義區域。
DK1.4引入NIO,可使用Native函式庫直接分配堆外記憶體,通過儲存在Java堆中的DirectByteBuffer物件引用這塊記憶體,避免Java堆和Native堆中來回複製資料
異常
不受Java堆大小限制,但受本地記憶體限制,申請記憶體時無足夠記憶體也會丟擲 OutOfMemoryError

9. 擴充套件概念

  1. CPU的PC
  2. Java記憶體模型
  3. 基於棧和基於暫存器的虛擬機器
  4. 堆和棧的區別
  5. java物件記憶體佈局、引用
  6. 異常級別、全都可以被捕獲
  7. String 常量池
  8. 符號引用與直接引用
  9. Java 堆和 Native 堆