JVM 運行時數據區:程序計數器、Java 虛擬機棧和本地方法棧,方法區、堆以及直接內存


Java 虛擬機可以看作一台抽象的計算機,如同真實的計算機,它也有自己的指令集和運行時內存區域。

Java 虛擬機在執行 Java 程序的過程中會把它所管理的內存(運行時內存區域)划分為若干個不同的數據區域。

如下圖所示:

一、程序計數器 Program Counter Register

1.定義:程序計數器是當前線程所執行字節碼的行號指示器。

2.線程私有內存的原因:Java 中的多線程是線程間輪流切換並需要 CPU 給予時間片的方式實現的。在任何一個確定的時刻,都只有一個線程在執行指令。為了線程間輪流切換后能夠快速恢復到正確執行的位置,每一個線程都有自己的程序計數器,各線程間程序計數器互不影響,獨立存在。因此,程序計數器是 “線程私有” 的內存區域。

3.詳解:在當前線程執行 Java 指令時,程序計數器中存儲的是正在執行的字節碼的地址;而當前線程執行 native 方法時,程序計數器存儲值為空。

字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程回復等基礎功能都需要依賴這個計數器來完成。

4.可能導致的錯誤:程序計數器是 Java 運行時內存區域中唯一一個不會拋出 OutOfMemoryError (OOM)情況的內存區域。

 

二、Java 虛擬機棧 JVM Stacks

1.定義:Java 虛擬機棧描述的是 Java 方法執行的內存模型。

2.線程私有內存的原因:在當前線程執行方法時,Java 虛擬機棧會創建一個對應該方法的棧幀(Stack Frame)。棧幀中有 局部變量表、操作數棧、動態鏈接和方法出口等信息。

每一個方法從調用到執行完成時,都對應着棧幀從入棧到出棧的過程。

3.詳解:局部變量表存放了各種編譯器可知的基本數據類型、對象引用類型和 returnAddress 類型。局部變量表大小在編譯期就已確定了,當進入一個方法時,棧幀需要分配給局部變量表的內存空間早已固定,不會再做更改。

(關於 JVM Stacks 的詳細情況可能放在 本系列 JVM 執行引擎 一文 中)

4.可能拋出的錯誤:

  1.當請求的棧深度超越規定的棧深度時,便會拋出 StackOverflowError (sof) 異常

  2.虛擬機棧動態擴展時,如果無法申請到足夠的內存,拋出 OOM 異常

 

三、本地方法棧

1. 定義:本地方法棧描述的是運行時 native 方法的內存模型。

2.解釋:本地方法棧與 Java 虛擬機棧幾乎相同,只不過本地方法棧對應的是本地方法,Java 虛擬機棧對應的是 Java 方法。

  Hotspot 虛擬機將 java 虛擬機棧與本地方法棧合二為一。

3.可能拋出的異常同 Java 虛擬機棧一樣(SOF,OOM)

本地方法的解釋:Java 本地方法

 

四、Java 堆

1. 定義:Java 堆是一塊用來存放對象實例的運行時內存空間。

2.Java 堆唯一目的就是用來存放對象實例。幾乎所有的對象實例(包括數組)都會在這里分配內存。屬於“線程共享"的內存區域。

Java 堆是垃圾回收器管理的主要區域。Java 虛擬機可以處於物理上的不連續內存空間中,只要邏輯上是連續的內存空間即可。

java 堆是垃圾回收的主要區域

3.可能拋出的異常:

  如果堆中沒有內存完成實例分配,並且堆再也無法擴展時,那么會拋出 OOM 異常

 

五、方法區

 1.定義:方法區用於存儲已被虛擬機加載的類的結構信息、常量、靜態變量、即時編譯器編譯后的靜態代碼等數據。

方法區是線程共享的內存區域。

2.解釋:在 Hotspot 虛擬機上,方法區采用永久代的方式實現,因此方法區又被稱為永久代(Permanent Generation),實際上這兩者並不等價。因為這樣實現的方法區不僅容易遇到內存溢出的問題,而且極少數方法(String.intern) 也會在不同的虛擬機上有不同的表現。

Hotspot 虛擬機現在也有放棄永久代而采用 Native Memory 來實現方法區的規划了(在 JDK1.7 中,將放在永久代的字符串常量池移出)。

對於垃圾回收而言,方法區主要集中在常量池的回收和類類型的卸載這兩方面。但是方法區的垃圾回收效果可謂是差強人意,特別是類類型的卸載,不過這部分類型的回收又是必須的。

3. 可能拋出的異常:

  方法區如果無法滿足內存分配需求時,將會拋出 OOM 異常。

 

5.1 方法區重要組成部分: 運行時常量池 Runtime Constant Pool

1.定義:運行時常量池是 class 文件中每一個類或接口的常量池表的運行時表現形式,包括若干種不同的常量,從編譯期可知的數值字面量到必須在運行期解析后才能獲得的方法和字段引用。

  每一個運行時常量池都在 Java 虛擬機的方法區中分配,在加載類和接口到虛擬機后,就將相應的常量放入運行時常量池。

2.可能拋出的異常:

  當常量池無法再申請到內存時會拋出 OOM 異常

 3.例子:

 1 public class A {
 2     public static void main(String[] args) {
 3         String a = "abc";
 4         String b = "abc";
 5         System.out.println(a==b);
 6         String c = new String("abc");
 7         System.out.println(a==c);
 8         System.out.println(a==c.intern());
 9     }
10 }

 

六、直接內存 Direct Memory

在 JDK1.4 中新加入了 NIO(New Input/Output) 類,引入了一種基於通道(Channel) 與緩沖區(Buffer) 的 I/O 方式,它可以使用 Native 函數庫直接分配堆外內存,然后通過一個存儲在 Java 堆中的 DirectByteBuffer 對象作為這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在 Java 堆和 Native 堆中來回復制數據。

顯然,本機直接內存的分配不會受到 Java 堆大小的限制,但是,既然是內存,坑定還是受到本機總內存(包括 RAM 以及 SWAP 區或者分頁文件)大小以及處理器尋址空間的限制。服務器管理員在配置虛擬機參數時,會根據實際內存設置 -Xmx 等參數信息,但經常忽略直接內存,使得各個內存區域總和大於物理內存限制(包括物理的和操作系統級的限制),從而導致動態擴展時出現 OOM 的異常。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM