Java虛擬機(一)結構原理與運行時數據區域
我們來學習Java虛擬機的結構原理與運行時數據區域。
1.Java虛擬機概述
Oracle官方定義的Java技術體系主要包括以下幾個部分:
- Java程序設計語言
- 各種平臺的Java虛擬機
- Class文件格式
- Java API類庫
- 第三方Java類庫
可以把Java程序設計語言、Java虛擬機和Java API類庫這三部分統稱為JDK(Java Development Kit),它是Java程序開發的最小環境。另外,Java API中的Java SE API子集和Java虛擬機這兩部分統稱為JRE(Java Runtime Environment),它是Java程序運行的標準環境。
從上面可以看出Java虛擬機及其重要,它是整個Java平臺的基石,是Java語言編譯代碼的運行平臺。你可以把Java虛擬機看做一個抽象的計算機,它有各種指令集和各種運行時數據區域。
1.1 Java虛擬機家族
很多同學可能認為Java虛擬機,就是一個虛擬機而已,它還有家族?或者認為Java虛擬機指的就是Oracle的HotSpot虛擬機。這里來簡單介紹Java虛擬機家族,自從1996年Sun公司發布的JDK1.0中包含的Sun Classic VM到今天,出現和消亡了很多種虛擬機,我們這里只簡單介紹目前存活的相對主流Java虛擬機。
HotSpot VM
Oracle JDK和OpenJDK中自帶的虛擬機,是最主流的和使用范圍最廣的Java虛擬機。介紹Java虛擬機的技術文章,如果不做特殊說明,大部分都是介紹HotSpot VM的。HotSpot VM并非是Sun公司開發的,而是由Longview Technologies這家小公司設計的,它在1997年被Sun公司收購,Sun公司又在2009年被Oracle收購。
J9 VM
J9 VM 是IBM開發的VM,目前是其主力發展的Java虛擬機。J9 VM的市場定位和HotSpot VM接近,它是一款設計上從服務端到桌面應用再到嵌入式都考慮到的多用途虛擬機,目前J9 VM的性能水平大致跟HotSpot VM是一個檔次的。
Zing VM
以Oracle的HotSpot VM為基礎,改進了許多會影響延遲的細節。最大的三個賣點是:
- 1.低延遲,“無暫停”的C4 GC,GC帶來的暫停可以控制在10ms以下的級別,支持的Java堆大小可以到1TB;
- 2.啟動后快速預熱功能。
- 3.可管理性:零開銷、可在生產環境全時開啟的、整合在JVM內的監控工具Zing Vision。
1.2 Java虛擬機執行流程
當我們執行一個Java程序時,它的執行流程是怎樣的呢?如下圖所示。

從上圖可以看到Java虛擬機與java語言沒有什么必然聯系,它只與特定的二進制文件:Class文件有關。
2.Java虛擬機結構
這里所講的體系結構,是指的Java虛擬機的抽象行為,而不是具體的比如HotSpot VM的實現。按照Java虛擬機規范,抽象的Java虛擬機如下圖所示。

2.1 Class文件格式
Java文件被編譯后生成了Class文件,這種二進制格式文件不依賴于特定的硬件和操作系統。每一個Class文件中都對應著唯一一個類或者接口的的定義信息,但是類或者接口并不一定定義在文件中,比如類和接口可以通過類加載器來直接生成。
ClassFile的文件結構如下所示。
ClassFile { u4 magic; //魔數,固定值為0xCAFEBABE,用來判斷當前文件是能被Java虛擬機處理的Class文件 u2 minor_version; //副版本號 u2 major_version; //主版本號 u2 constant_pool_count; //常量池計數器 cp_info constant_pool[constant_pool_count-1]; //常量池 u2 access_flags; //類和接口層次的訪問標志 u2 this_class; //類索引 u2 super_class; //父類索引 u2 interfaces_count; //接口計數器 u2 interfaces[interfaces_count]; //接口表 u2 fields_count; //字段計數器 field_info fields[fields_count]; //字段表 u2 methods_count; //方法計數器 method_info methods[methods_count]; //方法表 u2 attributes_count; //屬性計數器 attribute_info attributes[attributes_count]; //屬性表 }
2.2 類加載器子系統
類加載器子系統通過多種類加載器來查找和加載Class文件到 Java 虛擬機中。Java虛擬機有兩種類加載器:系統加載器和用戶自定義加載器。其中系統加載器包括以下三種:
- 引導類加載器(Bootstrap Class Loader):用C/C++代碼實現的加載器,用以加載Java虛擬機運行時所需要的系統類,這些系統類在{JRE_HOME}/lib目錄下。Java虛擬機的啟動就是通過引導類加載器創建一個初始類來完成的。由于類加載器是使用平臺相關的底層C/C++語言實現的, 所以該加載器不能被Java代碼訪問到。但是,我們可以查詢某個類是否被引導類加載器加載過。引導類裝載器并不繼承java.lang.ClassLoader。
- 擴展類加載器(Extensions Class Loader):用于加載 Java 的拓展類 ,拓展類一般會放在 {JRE_HOME}/lib/ext/ 目錄下,用來提供除了系統類之外的額外功能。
- 應用程序類加載器(Application Class Loader):該類加載器是用于加載用戶代碼,是用戶代碼的入口。應用類加載器將拓展類加載器當成自己的父類加載器,當嘗試加載類的時候,首先嘗試讓拓展類加載器加載,如果拓展類加載器加載成功,則直接返回加載結果Class instance,如果加載失敗,則會詢問引導類加載器是否已經加載了該類,如果沒有,應用類加載器才會嘗試自己加載。
用戶自定義加載器,則是通過繼承 java.lang.ClassLoader類的方式來實現自己的類加載器。
類加載器子系統除了要加載Class文件類到 Java 虛擬機中,還必須負責驗證被導入的Class類的正確性,為類變量分配并初始化內存,以及幫助解析符號引用。這些動作必須嚴格按以下順序進行:
1.裝載:查找并加載Class文件。
2.鏈接:驗證、準備、以及解析。
- 驗證:確保被導入類型的正確性。
- 準備:為類的靜態字段分配字段,并用默認值初始化這些字段。
- 解析:根據運行時常量池的符號引用來動態決定具體值得過程。
3.初始化:將類變量初始化為正確初始值。
2.3 數據類型
Java虛擬機與Java語言的數據類型相似,可以分為兩類:基本類型和引用類型。Java虛擬機希望編譯器在編譯期間盡可能的完成類型檢查,使得虛擬機在運行期間無需進行類型檢查操作。
2.4 運行時數據區域
很多人將Java的內存分為堆內存(heap)和棧內存(Stack),這種分發不夠準確,Java的內存區域劃分實際上遠比這復雜。
Java虛擬機在執行Java程序的過程中會把它所管理的內存劃分為不同的數據區域,根據《Java虛擬機規范(Java SE7版)》的規定,這些數據區域分別為程序計數器、Java虛擬機棧、本地方法棧、Java堆和方法區,下面我們來一一的對它們進行介紹。
2.4.1 程序計數器
為了保證程序能夠連續地執行下去,處理器必須具有某些手段來確定下一條指令的地址,而程序計數器正是起到這種作用。
程序計數器(Program Counter Register)也叫做PC寄存器,是一塊較小的內存空間。在虛擬機概念模型中,字節碼解釋器工作時就是通過改變程序計數器來選取下一條需要執行的字節碼指令,Java虛擬機的多線程是通過輪流切換并分配處理器執行時間的方式來實現的,在一個確定的時刻只有一個處理器執行一條線程中的指令,為了在線程切換后能恢復到正確的執行位置,每個線程都會有一個獨立的程序計數器,因此,程序計數器是線程私有的。如果線程執行的方法不是Native方法,則程序計數器保存正在執行的字節碼指令地址,如果是Native方法則程序計數器的值則為空(Undefined)。程序計數器是Java虛擬機規范中唯一沒有規定任何OutOfMemoryError情況的數據區域。
2.4.2 Java虛擬機棧
每一條Java虛擬機線程都有一個線程私有的Java虛擬機棧(Java Virtual Machine Stacks)。它的生命周期與線程相同,與線程是同時創建的。Java虛擬機棧存儲線程中Java方法調用的狀態,包括局部變量、參數、返回值以及運算的中間結果等。一個Java虛擬機棧包含了多個棧幀,一個棧幀用來存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。當線程調用一個Java方法時,虛擬機壓入一個新的棧幀到該線程的Java棧中,當該方法執行完成,這個棧幀就從Java棧中彈出。我們平常所說的棧內存(Stack)指的就是Java虛擬機棧。
Java虛擬機規范中定義了兩種異常情況:
- 如果線程請求分配的棧容量超過Java虛擬機所允許的的最大容量,Java虛擬機會拋出StackOverflowError。
- 如果Java虛擬機棧可以動態擴展(大部分Java虛擬機都可以動態擴展),但是擴展時無法申請到足夠的內存,或者在創建新的線程時沒有足夠的內存去創建對應的Java虛擬機棧,則會拋出OutOfMemoryError異常。
2.4.3 本地方法棧
Java虛擬機實現可能要用到C Stacks來支持Native語言,這個C Stacks就是本地方法棧(Native Method Stack)。它與Java虛擬機棧類似,只不過本地方法棧是用來支持Native方法服務。如果Java虛擬機不支持Native方法,并且也不依賴于C Stacks,可以無需支持本地方法棧。在Java虛擬機規范中對本地方法棧的語言和數據結構等沒有強制規定,因此具體的Java虛擬機可以自由實現它,比如HotSpot VM將本地方法棧和Java虛擬機棧合二為一。
與Java虛擬機棧類似,本地方法棧也會拋出 StackOverflowError和OutOfMemoryError異常
2.4.4 Java堆
Java堆(Java Heap)是被所有線程共享的運行時內存區域。Java堆用來存放對象實例,幾乎所有的對象實例都在這里分配內存。Java堆存儲的對象被垃圾收集器管理,這些受管理的對象無需也無法顯示的銷毀。從內存回收的角度,Java堆可以粗略的分為新生代和老年代。從內存分配的角度Java堆中可能劃分出多個線程私有的分配緩沖區。不管如何劃分,Java堆存儲的內容是不變的,進行劃分是為了能更快的回收或者分配內存。
Java堆的容量可以時固定的,也可以動態的擴展。Java堆的所使用的內存在物理上不需要連續,邏輯上連續即可。
Java虛擬機規范中定義了一種異常情況:
- 如果在堆中沒有足夠的內存來完成實例分配,并且堆也無法進行擴展時,則會拋出OutOfMemoryError異常。
2.4.5 方法區
方法區(Method Area)是被所有線程共享的運行時內存區域。用來存儲已經被Java虛擬機加載的類的結構信息,包括:
運行時常量池、字段和方法信息、靜態變量等數據。方法區是Java堆的邏輯組成部分,它一樣在物理上不需要連續,并且可以選擇在方法區中不實現垃圾收集。方法區并不等同于永久代,只是因為HotSpot VM使用永久代來實現方法區,對于其他的Java虛擬機,比如J9和JRockit等,并不存在永久代概念。
Java虛擬機規范中定義了一種異常情況:
- 如果方法區的內存空間不滿足內存分配需求時,Java虛擬機會拋出OutOfMemoryError異常。
運行時常量池
運行時常量池(Runtime Constant Pool)是方法區的一部分。在2.1 Class文件格式這一小節中我們得知,Class文件不僅包含了類的版本、接口、字段和方法等信息,還包含了常量池,它用來存放編譯時期生成的字面量和符號引用,這些內容會在類加載后存放在方法區的運行時常量池中。運行時常量池可以理解為是類或接口的常量池的運行時表現形式。
Java虛擬機規范中定義了一種異常情況:
當創建類或接口時,如果構造運行時常量池所需的內存超過了方法區所能提供的最大值,Java虛擬機會拋出OutOfMemoryError異常。
浙公網安備 33010602011771號