JVM(Java Virtual Machine)的運行時數據區(Runtime Data Areas)被劃分為幾個不同的區域,每個區域都有其特定的用途和存儲的數據類型。以下是 JVM 各個區域存儲數據的詳細說明:
1. 程序計數器 (Program Counter Register)
-
存儲內容:
- 當前線程正在執行的 Java 方法的 字節碼指令的地址(行號)。
- 如果當前線程正在執行的是 Native 方法,程序計數器的值為 undefined (空)。
-
特點:
- 線程私有:每個線程都有自己獨立的程序計數器。
- 非常小:占用內存空間非常小。
- 唯一:是唯一一個在 Java 虛擬機規范中沒有規定任何
OutOfMemoryError
情況的區域。
-
作用:
- 控制程序的執行流程:JVM 通過程序計數器來控制程序的執行順序,實現分支、循環、跳轉、異常處理、線程恢復等功能。
- 線程切換:Java 虛擬機的多線程是通過線程輪流切換、分配處理器執行時間來實現的。在任何一個確定的時刻,一個處理器(或一個內核)只會執行一條線程中的指令。為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器。
2. 虛擬機棧 (VM Stack)
-
存儲內容:
- 棧幀 (Stack Frame): 每個方法調用都會創建一個棧幀,用于存儲方法的局部變量、操作數棧、動態鏈接、方法出口等信息。方法執行完畢后,棧幀會被銷毀(出棧)。
- 局部變量表 (Local Variable Table):
- 存放方法參數和方法內部定義的局部變量。
- 基本數據類型變量:直接存儲值。
- 引用類型變量:存儲對象的引用(地址)。
- 局部變量表的大小在編譯時確定。
- 操作數棧 (Operand Stack):
- 一個后入先出 (LIFO) 棧,用于存放方法執行過程中的操作數和中間結果。
- 例如,執行加法操作時,會將兩個操作數壓入操作數棧,然后彈出兩個操作數進行計算,并將結果壓入操作數棧。
- 動態鏈接 (Dynamic Linking):
- 指向運行時常量池中該棧幀所屬方法的引用。
- 用于支持方法調用過程中的動態鏈接(例如,解析方法調用、接口調用等)。
- 方法出口 (Return Address):
- 記錄方法返回時應該跳轉到的指令地址。
- 正常返回:返回到調用者的指令地址。
- 異常返回:返回到異常處理器的指令地址。
- 附加信息: 可能包含一些附加信息, 例如調試信息等.
- 局部變量表 (Local Variable Table):
- 棧幀 (Stack Frame): 每個方法調用都會創建一個棧幀,用于存儲方法的局部變量、操作數棧、動態鏈接、方法出口等信息。方法執行完畢后,棧幀會被銷毀(出棧)。
-
特點:
- 線程私有:每個線程都有自己的虛擬機棧。
- 后入先出 (LIFO):棧幀的創建和銷毀遵循后入先出的原則。
- 速度快:訪問速度僅次于程序計數器。
- 大小:棧的大小可以是固定的,也可以是動態擴展的。
-
異常:
StackOverflowError
: 如果線程請求的棧深度大于虛擬機允許的深度,拋出此異常(例如,無限遞歸調用)。OutOfMemoryError
: 如果虛擬機棧可以動態擴展,但無法申請到足夠的內存,拋出此異常。
3. 本地方法棧 (Native Method Stack)
- 存儲內容:
- 與虛擬機棧類似,但用于支持 native 方法(使用 C、C++ 等編寫的方法)的執行。
- 存儲 native 方法的局部變量、參數、返回值等信息。
- 特點:
- 線程私有:每個線程都有自己的本地方法棧。
- 具體的實現方式由虛擬機決定(HotSpot VM 中,本地方法棧和虛擬機棧是合二為一的)。
- 異常:
StackOverflowError
OutOfMemoryError
4. 堆 (Heap)
-
存儲內容:
- 對象實例: 幾乎所有的對象實例都在堆上分配內存。
- 數組: 數組也在堆上分配內存。
-
特點:
- 線程共享:所有線程共享同一個堆。
- 最大:是 JVM 中最大的一塊內存區域。
- 垃圾回收:是垃圾回收的主要區域。
- 邏輯連續:邏輯上是連續的,物理上可以不連續。
- 劃分:通常劃分為新生代(Young Generation)和老年代(Old Generation)。
- 新生代:存放新創建的對象。
- Eden 區:大多數對象首先在 Eden 區分配。
- Survivor 區 (From Survivor 和 To Survivor):存放經過一次 Minor GC 后仍然存活的對象。
- 老年代:存放生命周期較長的對象,或大對象。
- 新生代:存放新創建的對象。
-
異常:
OutOfMemoryError
: 如果堆中沒有足夠的內存分配給新的對象,并且堆也無法再擴展時,拋出此異常。
5. 方法區 (Method Area)
-
存儲內容:
- 類信息:
- 類的全限定名
- 類的父類
- 類的接口
- 類的修飾符
- 類的字段信息
- 類的方法信息
- 類的注解信息
- 常量:
- 編譯時常量(例如,
final
修飾的基本類型或字符串字面量)。 - 運行時常量(例如,String.intern() 方法返回的字符串)。
- 編譯時常量(例如,
- 靜態變量:
- 類變量(
static
修飾的變量)。
- 類變量(
- 即時編譯器編譯后的代碼 (JIT Compiled Code):
- 熱點代碼(經常執行的代碼)會被 JIT 編譯器編譯為本地機器碼,并緩存在方法區中。
- 類信息:
-
特點:
- 線程共享:所有線程共享同一個方法區。
- 非堆 (Non-Heap): 在 HotSpot VM 中,方法區也被稱為“非堆”。
- 永久代/元空間:
- JDK 1.7 及之前: HotSpot VM 使用“永久代”(Permanent Generation)來實現方法區。永久代使用 JVM 內存。容易出現
OutOfMemoryError: PermGen space
。 - JDK 1.8 及之后: HotSpot VM 使用“元空間”(Metaspace)來實現方法區。元空間使用本地內存(Native Memory),不容易出現
OutOfMemoryError
,但需要注意防止本地內存耗盡。
- JDK 1.7 及之前: HotSpot VM 使用“永久代”(Permanent Generation)來實現方法區。永久代使用 JVM 內存。容易出現
- 運行時常量池 (Runtime Constant Pool): 方法區的一部分,存放編譯期生成的各種字面量和符號引用,以及運行時產生的常量。
- 垃圾回收: 方法區也會進行垃圾回收,主要回收廢棄的常量和無用的類。
-
異常:
OutOfMemoryError
: 如果方法區無法滿足內存分配需求,拋出此異常。- JDK 1.7 及之前,如果永久代空間不足,會拋出
OutOfMemoryError: PermGen space
。 - JDK 1.8 及之后,如果元空間不足,會拋出
OutOfMemoryError: Metaspace
。
總結:
區域 | 存儲內容 | 線程共享性 |
---|---|---|
程序計數器 | 當前線程正在執行的字節碼指令的地址(行號)。如果是 Native 方法,則值為空 (Undefined)。 | 私有 |
虛擬機棧 | 每個方法調用對應一個棧幀。棧幀中存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。局部變量表存放方法參數和局部變量。操作數棧用于存放方法執行過程中的操作數和中間結果。 | 私有 |
本地方法棧 | 與虛擬機棧類似,但用于支持 native 方法的執行。 | 私有 |
堆 | 對象實例、數組。 | 共享 |
方法區 | 類信息(類名、父類、接口、字段、方法、注解等)、常量、靜態變量、即時編譯器編譯后的代碼。運行時常量池是方法區的一部分,存放編譯期生成的各種字面量和符號引用,以及運行時產生的常量。 | 共享 |