Java 虛擬機定義了在程序執行期間使用的各種運行時數據區域。
其中一些數據區域所有線程共享,在 Java 虛擬機(JVM)啟動時創建,僅在 Java 虛擬機退出時銷毀。
還有一些數據區域是每個線程的。線程數據區域是在線程啟動時創建,線程結束時銷毀。
一、運行時數據區划分(JDK8)
1、The pc Register(PC 寄存器、程序計數器)
2、Java Virtual Machine Stacks(Java 虛擬機棧、Java 棧)
3、Native Method Stacks(本地方法棧,C棧)
4、Heap(堆)
5、Method Area(方法區,JDK8 中的實現叫元數據區(本地內存中),JDK7 中的實現叫永久代(JVM中))
6、Run-Time Constant Pool(運行時常量池,方法區的一部分)
二、區划分詳情
2.1.The PC Register(PC 寄存器)
每個 JVM 線程都有自己的 pc 寄存器(內存為線程私有,隨着線程的創建而創建,線程的結束而銷毀)。
在任何時候,每個 JVM 線程都在執行單個方法的代碼,即該線程的當前方法(字節碼解釋器通過改變程序計數器來選取下一條需要執行指令,從而實現代碼的流程控制,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成)。
如果該方法不是 native,則 pc 寄存器包含當前正在執行的 JVM 指令的地址(線程切換就知道上次線程執行到哪了)。
如果該方法是 native,則 pc 寄存器為 Undefined(不會 OutOfMemoryError)。
2.2.Java Virtual Machine Stacks(Java 虛擬機棧)
描述 Java 方法執行的內存模型。
每個 JVM 線程都有一個私有 JVM 棧,與線程同時創建(內存為線程私有,隨着線程的創建而創建,線程的結束而銷毀)。
JVM 棧存儲 frames (棧幀)。方法調用和返回對應壓棧和出棧(棧頂的棧幀是當前正在執行的活動棧,也就是當前正在執行的方法,PC 寄存器也會指向這個地址,只有這個活動的棧幀的本地變量可以被操作數棧使用)。
由於除了壓棧和出棧之外,永遠不會直接操作 JVM 棧,JVM 棧的內存不需要是連續的。
JVM 規范允許 JVM 棧具有固定大小,也可以根據計算的需要動態擴展和收縮(通過 -Xss 控制)。
以下異常與 JVM 棧有關:
如果不可以動態擴展 Java 虛擬機棧,當線程中的方法調深度用超過 Java 虛擬機棧最大深度時,會拋出 StackOverflowError 異常(出現 StackOverFlowError 時,內存空間可能還有很多)。
如果可以動態擴展 Java 虛擬機棧,當線程嘗試進行擴展但可使內存不足以實現擴展,或者可使內存不足以為新線程創建初始 Java 虛擬機堆棧時,會拋出 OutOfMemoryError 異常。
2.3.Native Method Stacks(本地方法棧)
描述本地方法運行過程的內存模型。
JVM 可以使用常規棧來支持 native 方法(用 Java 編程語言以外的語言編寫的方法,執行也會創建棧幀)。
無法加載 native 方法,並且本身不依賴於傳統堆棧的 JVM, 不需要提供本地方法棧。如果提供,則通常在每個線程創建時分配本地方法棧。
本地方法棧具有固定大小,也可以根據計算的需要動態擴展和收縮。
以下異常與本地方法棧有關:
如果不可以動態擴展本地方法棧,當線程中的計算需要比允許的本地方法棧更大,則會拋出 StackOverflowError 異常。
如果可以動態擴展本地方法棧,當嘗試進行本地方法棧擴展,但可使內存不足,或沒有足夠的內存可用於為當前前程創建初始本地方法棧,則會拋出 OutOfMemoryError 異常。
2.4.Heap(堆)
堆是運行時數據區,從中分配所有類實例和數組的內存(JVM 中內存最大的一塊,被所有線程共享,需要注意同步問題)。
堆是在 JVM 啟動時創建的。
堆中對象的存儲由垃圾收集器(GC,自動存儲管理系統)回收,對象永遠不會被顯式釋放。
JVM 沒有特定類型的 GC,可以根據實現者的系統要求選擇存儲管理技術。
堆可以具有固定大小,也可以根據計算的需要進行擴展(通過 -Xmx 和 -Xms 控制)。堆的內存不需要是連續的。
以下異常情況與堆有關:
如果計算需要的堆量超過自動存儲管理系統可用的堆,則會拋出 OutOfMemoryError 異常。
2.5.Method Area(方法區)
方法區在所有 JVM 線程之間共享。方法區是在 JVM 啟動時創建的。方法區在邏輯上是堆的一部分,但可選擇不實現垃圾回收。
方法區存儲類結構,如運行時常量(Run-Time Constant Pool),字段和方法數據,以及方法和構造函數的代碼,包括類和實例初始化以及接口初始化中使用的特殊方法。
JVM 規范未規定方法區的位置或用於管理編譯代碼的策略。
方法區可以是固定大小的,也可以根據計算的需要進行擴展。方法區的內存不需要是連續的。
以下異常與方法區有關:
如果方法區域中的內存無法滿足分配請求,會拋出 OutOfMemoryError 異常。
2.6.Run-Time Constant Pool(運行時常量池)
運行時常量池是方法區的一部分。
Class 文件中的常量池(constant_pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分在類加載后進入方法區的運行時常量池中。
在運行期間,也可以向常量池中添加新的常量。如 String 類的 intern() 方法。
每個運行時常量池都是從 JVM 的方法區中分配的。
以下異常與類或接口的運行時常量池的構造有關:
在創建類或接口時,如果運行時常量池的構造需要的內存比 JVM 方法區中可用的內存多,會拋出 OutOfMemoryError 異常。
三、直接內存(堆外內存)
不是 JVM 運行時數據區的一部分,但這部分內存也會被頻繁的使用,也可能導致 OutOfMemoryError 異常。
JDK 1.4 中新加入了 NIO 類,通過調用本地方法直接分配 Java 虛擬機之外的內存,然后通過一個存儲在堆中的 DirectByteBuffer 對象直接操作該內存。
避免了 Java 堆和 Native 堆來回交換數據的時間,更高效。
四、大概划分圖(JDK8-HotSpot)
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5
http://www.hollischuang.com/archives/2509
https://github.com/doocs/jvm/blob/master/docs/01-jvm-memory-structure.md