Java虛擬機-運行時數據區

1. 概述

根據Java虛擬機規范,Java程序在運行時,在內存中定義了若干個區域。這些區域的用途,生命周期各不相同。本文將盡量簡要地介紹這些數據區,避免過多細節堆砌,具體細節以后再給出。數據區域可以由下圖表示


JVMMemoryStruct.jpg

2. 運行時數據區

如上圖所示,大體上我們可以把Java內存區域劃分為方法區,堆區,Java虛擬機棧,本地方法棧,程序計數器。還有一個直接內存(DirectMemory, 也稱堆外內存,它不屬于運行時數據區的內容,使用過NIO的同學可能了解過)下面簡要說明各個數據區域。

2.2 堆區

堆區(Java Heap)最大的作用就是存放對象實例,但是并不是所有對象一定要存放到堆上,隨著JIT技術和逃逸分析技術的發展,對象可以存放到棧上。

堆區被所有線程共享。

堆區不一定要分配在物理連續的內存中,只要其邏輯連續即可。

當堆區沒有足夠內存完成實例分配時,并且堆無法擴展時,將會拋出OutOfMemoryError異常。

談到堆區時,就不得不提到垃圾回收技術,堆區是垃圾回收器管理的主要區域,因此也被稱為GC堆。不同的垃圾回收算法會把堆區劃分為各個不同的區域,比如新生代和老年代,具體細節將在垃圾回收算法部分詳解。

2.3 方法區(靜態區)

方法區(Method Area)用于存儲已被JVM加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。當我們創建對象實例后,對象的類型信息存儲在方法堆之中,實例數據存放在堆中;實例數據指的是在 Java 中創建的各種實例對象以及它們的值,類型信息指的是定義在 Java 代碼中的常量、靜態變量、以及在類中聲明的各種方法、方法字段等等;同時可能包括即時編譯器編譯后產生的代碼數據。

方法區被所有線程共享。

注意HotSpot虛擬機在實現時,將方法區的實現在堆區的永久代實現,因此有時候將方法區看作永久代的一部分。

2.3.1 運行時常量池

用于存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后進入方法區的運行時常量池中存放。

2.4 Java虛擬機棧

Java虛擬機棧(Java Virtual Machine Stack)由若干個Java虛擬機棧幀(Stack Frame)組成,而棧幀是JVM方法調用和方法執行的數據結構,每一個方法的調用和返回都對應著棧幀入棧出棧的過程。棧頂的棧幀稱為當前棧幀(Current Stack Frame),對應當前方法。

每一個棧幀主要由局部變量表,操作數棧,動態鏈接和返回地址和棧幀信息構成。

2.4.1 局部變量表

局部變量表用于存放方法參數局部變量,虛擬機通過索引定位的方式使用局部變量表。

變量槽(Variable Slot)是局部變量表的最小單位,沒有強制規定大小為 32 位,雖然32位足夠存放大部分類型的數據。一個 Slot 可以存放 boolean、byte、char、short、int、float、reference 和 returnAddress 8種類型。其中 reference 表示對一個對象實例的引用,通過它可以得到對象在Java 堆中存放的起始地址的索引和該數據所屬數據類型在方法區的類型信息。returnAddress 則指向了一條字節碼指令的地址。 對于64位的 long 和 double 變量而言,虛擬機會為其分配兩個連續的 Slot 空間。

2.4.2 操作數棧

方法執行過程中,進行算術運算或者是調用其他的方法進行參數傳遞的時候是通過操作數棧進行的。

如果線程請求的棧深度大于虛擬機規定的最大深度,將會拋出StackOverflowError異常;如果虛擬機棧可以動態擴展,并且擴展時無法申請到足夠的內存,就會跑出OutOfMemoryError異常

2.4.3 動態鏈接

每個棧幀都包含一個執行運行時常量池中該棧幀所屬方法的引用,持有這個引用是為了支持方法調用過程中的動態連接(Dynamic Linking)。
Class 文件中存放了大量的符號引用,字節碼中的方法調用指令就是以常量池中指向方法的符號引用作為參數。這些符號引用一部分會在類加載階段或第一次使用時轉化為直接引用,這種轉化稱為靜態解析。另一部分將在每一次運行期間轉化為直接引用,這部分稱為動態連接。

2.4.4 方法返回地址

當一個方法開始執行以后,只有兩種方法可以退出當前方法:
當執行遇到返回指令,會將返回值傳遞給上層的方法調用者,這種退出的方式稱為正常完成出口(Normal Method Invocation Completion),一般來說,調用者的PC計數器可以作為返回地址。
當執行遇到異常,并且當前方法體內沒有得到處理,就會導致方法退出,此時是沒有返回值的,稱為異常完成出口(Abrupt Method Invocation Completion),返回地址要通過異常處理器表來確定。

2.4.5 棧幀信息

虛擬機規范并沒有規定具體虛擬機實現包含什么附加信息,這部分的內容完全取決于具體實現。在實際開發中,一般會把動態連接,方法返回地址和附加信息全部歸為一類,稱為棧幀信息。

2.5 程序計數器

每一個Java線程都擁有自己的程序計數器,他可以看作多當前線程所執行的字節碼行號指示器。字節碼解釋器通過改變程序計數器的值來選取下一條需要執行的指令。分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器完成。

2.6 直接內存

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

2.7 本地方法棧

本地方法棧與JVM棧發揮的作用是非常相似的,他們的區別是JVM棧為執行Java方法服務,本地方法棧為執行Native方法服務。

3. 參考文章

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容