前言
本來計劃要寫Android內(nèi)存優(yōu)化的壶栋,覺得有必要在此之前介紹一下Java虛擬機的相關(guān)知識辰如,Java虛擬機也并不是三言兩語能夠介紹完的,因此開了Java虛擬機系列委刘,這一篇文章我們來學(xué)習(xí)Java虛擬機的結(jié)構(gòu)原理與運行時數(shù)據(jù)區(qū)域丧没。
1.Java虛擬機概述
Oracle官方定義的Java技術(shù)體系主要包括以下幾個部分:
- Java程序設(shè)計語言
- 各種平臺的Java虛擬機
- Class文件格式
- Java API類庫
- 第三方Java類庫
可以把Java程序設(shè)計語言、Java虛擬機和Java API類庫這三部分統(tǒng)稱為JDK(Java Development Kit)锡移,它是Java程序開發(fā)的最小環(huán)境呕童。另外,Java API中的Java SE API子集和Java虛擬機這兩部分統(tǒng)稱為JRE(Java Runtime Environment)淆珊,它是Java程序運行的標(biāo)準環(huán)境夺饲。
從上面可以看出Java虛擬機及其重要,它是整個Java平臺的基石施符,是Java語言編譯代碼的運行平臺往声。你可以把Java虛擬機看做一個抽象的計算機,它有各種指令集和各種運行時數(shù)據(jù)區(qū)域戳吝。
1.1 Java虛擬機家族
很多同學(xué)可能認為Java虛擬機浩销,就是一個虛擬機而已,它還有家族听哭?或者認為Java虛擬機指的就是Oracle的HotSpot虛擬機慢洋。這里來簡單介紹Java虛擬機家族,自從1996年Sun公司發(fā)布的JDK1.0中包含的Sun Classic VM到今天陆盘,出現(xiàn)和消亡了很多種虛擬機普筹,我們這里只簡單介紹目前存活的相對主流Java虛擬機。
HotSpot VM
Oracle JDK和OpenJDK中自帶的虛擬機隘马,是最主流的和使用范圍最廣的Java虛擬機太防。介紹Java虛擬機的技術(shù)文章,如果不做特殊說明酸员,大部分都是介紹HotSpot VM的蜒车。HotSpot VM并非是Sun公司開發(fā)的讳嘱,而是由Longview Technologies這家小公司設(shè)計的,它在1997年被Sun公司收購醇王,Sun公司又在2009年被Oracle收購呢燥。
**J9 VM **
J9 VM 是IBM開發(fā)的VM,目前是其主力發(fā)展的Java虛擬機寓娩。J9 VM的市場定位和HotSpot VM接近叛氨,它是一款設(shè)計上從服務(wù)端到桌面應(yīng)用再到嵌入式都考慮到的多用途虛擬機,目前J9 VM的性能水平大致跟HotSpot VM是一個檔次的棘伴。
Zing VM
以O(shè)racle的HotSpot VM為基礎(chǔ)寞埠,改進了許多會影響延遲的細節(jié)。最大的三個賣點是:
- 1.低延遲焊夸,“無暫腿柿”的C4 GC,GC帶來的暫挖逅耄可以控制在10ms以下的級別饭冬,支持的Java堆大小可以到1TB;
- 2.啟動后快速預(yù)熱功能揪阶。
- 3.可管理性:零開銷昌抠、可在生產(chǎn)環(huán)境全時開啟的、整合在JVM內(nèi)的監(jiān)控工具Zing Vision鲁僚。
1.2 Java虛擬機執(zhí)行流程
當(dāng)我們執(zhí)行一個Java程序時炊苫,它的執(zhí)行流程是怎樣的呢?如下圖所示冰沙。
從上圖可以看到Java虛擬機與java語言沒有什么必然聯(lián)系侨艾,它只與特定的二進制文件:Class文件有關(guān)。
2.Java虛擬機結(jié)構(gòu)
這里所講的體系結(jié)構(gòu)拓挥,是指的Java虛擬機的抽象行為唠梨,而不是具體的比如HotSpot VM的實現(xiàn)。按照Java虛擬機規(guī)范侥啤,抽象的Java虛擬機如下圖所示当叭。
2.1 Class文件格式
Java文件被編譯后生成了Class文件,這種二進制格式文件不依賴于特定的硬件和操作系統(tǒng)愿棋。每一個Class文件中都對應(yīng)著唯一一個類或者接口的的定義信息科展,但是類或者接口并不一定定義在文件中均牢,比如類和接口可以通過類加載器來直接生成糠雨。
ClassFile的文件結(jié)構(gòu)如下所示。
ClassFile {
u4 magic; //魔數(shù)徘跪,固定值為0xCAFEBABE甘邀,用來判斷當(dāng)前文件是能被Java虛擬機處理的Class文件
u2 minor_version; //副版本號
u2 major_version; //主版本號
u2 constant_pool_count; //常量池計數(shù)器
cp_info constant_pool[constant_pool_count-1]; //常量池
u2 access_flags; //類和接口層次的訪問標(biāo)志
u2 this_class; //類索引
u2 super_class; //父類索引
u2 interfaces_count; //接口計數(shù)器
u2 interfaces[interfaces_count]; //接口表
u2 fields_count; //字段計數(shù)器
field_info fields[fields_count]; //字段表
u2 methods_count; //方法計數(shù)器
method_info methods[methods_count]; //方法表
u2 attributes_count; //屬性計數(shù)器
attribute_info attributes[attributes_count]; //屬性表
}
2.2 類加載器子系統(tǒng)
類加載器子系統(tǒng)通過多種類加載器來查找和加載Class文件到 Java 虛擬機中琅攘。Java虛擬機有兩種類加載器:系統(tǒng)加載器和用戶自定義加載器。其中系統(tǒng)加載器包括以下三種:
- 引導(dǎo)類加載器(Bootstrap Class Loader):用C/C++代碼實現(xiàn)的加載器松邪,用以加載Java虛擬機運行時所需要的系統(tǒng)類坞琴,這些系統(tǒng)類在{JRE_HOME}/lib目錄下。Java虛擬機的啟動就是通過引導(dǎo)類加載器創(chuàng)建一個初始類來完成的逗抑。由于類加載器是使用平臺相關(guān)的底層C/C++語言實現(xiàn)的剧辐, 所以該加載器不能被Java代碼訪問到。但是邮府,我們可以查詢某個類是否被引導(dǎo)類加載器加載過荧关。引導(dǎo)類裝載器并不繼承java.lang.ClassLoader。
- 擴展類加載器(Extensions Class Loader):用于加載 Java 的拓展類 褂傀,拓展類一般會放在 {JRE_HOME}/lib/ext/ 目錄下忍啤,用來提供除了系統(tǒng)類之外的額外功能。
- 應(yīng)用程序類加載器(Application Class Loader):該類加載器是用于加載用戶代碼仙辟,是用戶代碼的入口同波。應(yīng)用類加載器將拓展類加載器當(dāng)成自己的父類加載器,當(dāng)嘗試加載類的時候叠国,首先嘗試讓拓展類加載器加載未檩,如果拓展類加載器加載成功,則直接返回加載結(jié)果Class<T> instance煎饼,如果加載失敗讹挎,則會詢問引導(dǎo)類加載器是否已經(jīng)加載了該類,如果沒有吆玖,應(yīng)用類加載器才會嘗試自己加載筒溃。
用戶自定義加載器,則是通過繼承 java.lang.ClassLoader類的方式來實現(xiàn)自己的類加載器沾乘。
類加載器子系統(tǒng)除了要加載Class文件類到 Java 虛擬機中怜奖,還必須負責(zé)驗證被導(dǎo)入的Class類的正確性,為類變量分配并初始化內(nèi)存翅阵,以及幫助解析符號引用歪玲。這些動作必須嚴格按以下順序進行:
1.裝載:查找并加載Class文件。
2.鏈接:驗證掷匠、準備滥崩、以及解析。
- 驗證:確保被導(dǎo)入類型的正確性讹语。
- 準備:為類的靜態(tài)字段分配字段钙皮,并用默認值初始化這些字段。
- 解析:根據(jù)運行時常量池的符號引用來動態(tài)決定具體值得過程。
3.初始化:將類變量初始化為正確初始值短条。
2.3 數(shù)據(jù)類型
Java虛擬機與Java語言的數(shù)據(jù)類型相似导匣,可以分為兩類:基本類型和引用類型。Java虛擬機希望編譯器在編譯期間盡可能的完成類型檢查茸时,使得虛擬機在運行期間無需進行類型檢查操作贡定。
2.4 運行時數(shù)據(jù)區(qū)域
很多人將Java的內(nèi)存分為堆內(nèi)存(heap)和棧內(nèi)存(Stack),這種分發(fā)不夠準確可都,Java的內(nèi)存區(qū)域劃分實際上遠比這復(fù)雜缓待。
Java虛擬機在執(zhí)行Java程序的過程中會把它所管理的內(nèi)存劃分為不同的數(shù)據(jù)區(qū)域,根據(jù)《Java虛擬機規(guī)范(Java SE7版)》的規(guī)定渠牲,這些數(shù)據(jù)區(qū)域分別為程序計數(shù)器命斧、Java虛擬機棧、本地方法棧嘱兼、Java堆和方法區(qū)国葬,下面我們來一一的對它們進行介紹。
2.4.1 程序計數(shù)器
為了保證程序能夠連續(xù)地執(zhí)行下去芹壕,處理器必須具有某些手段來確定下一條指令的地址汇四,而程序計數(shù)器正是起到這種作用。
程序計數(shù)器(Program Counter Register)也叫做PC寄存器踢涌,是一塊較小的內(nèi)存空間通孽。在虛擬機概念模型中,字節(jié)碼解釋器工作時就是通過改變程序計數(shù)器來選取下一條需要執(zhí)行的字節(jié)碼指令睁壁,Java虛擬機的多線程是通過輪流切換并分配處理器執(zhí)行時間的方式來實現(xiàn)的背苦,在一個確定的時刻只有一個處理器執(zhí)行一條線程中的指令,為了在線程切換后能恢復(fù)到正確的執(zhí)行位置潘明,每個線程都會有一個獨立的程序計數(shù)器行剂,因此,程序計數(shù)器是線程私有的钳降。如果線程執(zhí)行的方法不是Native方法厚宰,則程序計數(shù)器保存正在執(zhí)行的字節(jié)碼指令地址,如果是Native方法則程序計數(shù)器的值則為空(Undefined)遂填。程序計數(shù)器是Java虛擬機規(guī)范中唯一沒有規(guī)定任何OutOfMemoryError情況的數(shù)據(jù)區(qū)域铲觉。
2.4.2 Java虛擬機棧
每一條Java虛擬機線程都有一個線程私有的Java虛擬機棧(Java Virtual Machine Stacks)。它的生命周期與線程相同吓坚,與線程是同時創(chuàng)建的撵幽。Java虛擬機棧存儲線程中Java方法調(diào)用的狀態(tài),包括局部變量礁击、參數(shù)盐杂、返回值以及運算的中間結(jié)果等漏麦。一個Java虛擬機棧包含了多個棧幀,一個棧幀用來存儲局部變量表况褪、操作數(shù)棧、動態(tài)鏈接更耻、方法出口等信息测垛。當(dāng)線程調(diào)用一個Java方法時,虛擬機壓入一個新的棧幀到該線程的Java棧中秧均,當(dāng)該方法執(zhí)行完成食侮,這個棧幀就從Java棧中彈出。我們平常所說的棧內(nèi)存(Stack)指的就是Java虛擬機棧目胡。
Java虛擬機規(guī)范中定義了兩種異常情況:
- 如果線程請求分配的棧容量超過Java虛擬機所允許的的最大容量锯七,Java虛擬機會拋出StackOverflowError。
- 如果Java虛擬機椨海可以動態(tài)擴展(大部分Java虛擬機都可以動態(tài)擴展)眉尸,但是擴展時無法申請到足夠的內(nèi)存,或者在創(chuàng)建新的線程時沒有足夠的內(nèi)存去創(chuàng)建對應(yīng)的Java虛擬機棧巨双,則會拋出OutOfMemoryError異常噪猾。
2.4.3 本地方法棧
Java虛擬機實現(xiàn)可能要用到C Stacks來支持Native語言,這個C Stacks就是本地方法棧(Native Method Stack)筑累。它與Java虛擬機棧類似袱蜡,只不過本地方法棧是用來支持Native方法服務(wù)。如果Java虛擬機不支持Native方法慢宗,并且也不依賴于C Stacks坪蚁,可以無需支持本地方法棧。在Java虛擬機規(guī)范中對本地方法棧的語言和數(shù)據(jù)結(jié)構(gòu)等沒有強制規(guī)定镜沽,因此具體的Java虛擬機可以自由實現(xiàn)它敏晤,比如HotSpot VM將本地方法棧和Java虛擬機棧合二為一。
與Java虛擬機棧類似缅茉,本地方法棧也會拋出 StackOverflowError和OutOfMemoryError異常
2.4.4 Java堆
Java堆(Java Heap)是被所有線程共享的運行時內(nèi)存區(qū)域茵典。Java堆用來存放對象實例,幾乎所有的對象實例都在這里分配內(nèi)存宾舅。Java堆存儲的對象被垃圾收集器管理统阿,這些受管理的對象無需也無法顯示的銷毀。從內(nèi)存回收的角度筹我,Java堆可以粗略的分為新生代和老年代扶平。從內(nèi)存分配的角度Java堆中可能劃分出多個線程私有的分配緩沖區(qū)。不管如何劃分蔬蕊,Java堆存儲的內(nèi)容是不變的结澄,進行劃分是為了能更快的回收或者分配內(nèi)存。
Java堆的容量可以時固定的,也可以動態(tài)的擴展麻献。Java堆的所使用的內(nèi)存在物理上不需要連續(xù)们妥,邏輯上連續(xù)即可。
Java虛擬機規(guī)范中定義了一種異常情況:
- 如果在堆中沒有足夠的內(nèi)存來完成實例分配勉吻,并且堆也無法進行擴展時监婶,則會拋出OutOfMemoryError異常。
2.4.5 方法區(qū)
方法區(qū)(Method Area)是被所有線程共享的運行時內(nèi)存區(qū)域齿桃。用來存儲已經(jīng)被Java虛擬機加載的類的結(jié)構(gòu)信息惑惶,包括:
運行時常量池、字段和方法信息短纵、靜態(tài)變量等數(shù)據(jù)带污。方法區(qū)是Java堆的邏輯組成部分,它一樣在物理上不需要連續(xù)香到,并且可以選擇在方法區(qū)中不實現(xiàn)垃圾收集鱼冀。方法區(qū)并不等同于永久代,只是因為HotSpot VM使用永久代來實現(xiàn)方法區(qū)悠就,對于其他的Java虛擬機雷绢,比如J9和JRockit等,并不存在永久代概念理卑。
Java虛擬機規(guī)范中定義了一種異常情況:
- 如果方法區(qū)的內(nèi)存空間不滿足內(nèi)存分配需求時翘紊,Java虛擬機會拋出OutOfMemoryError異常。
運行時常量池
運行時常量池(Runtime Constant Pool)是方法區(qū)的一部分藐唠。在2.1 Class文件格式這一小節(jié)中我們得知帆疟,Class文件不僅包含了類的版本、接口宇立、字段和方法等信息踪宠,還包含了常量池,它用來存放編譯時期生成的字面量和符號引用妈嘹,這些內(nèi)容會在類加載后存放在方法區(qū)的運行時常量池中柳琢。運行時常量池可以理解為是類或接口的常量池的運行時表現(xiàn)形式。
Java虛擬機規(guī)范中定義了一種異常情況:
當(dāng)創(chuàng)建類或接口時润脸,如果構(gòu)造運行時常量池所需的內(nèi)存超過了方法區(qū)所能提供的最大值柬脸,Java虛擬機會拋出OutOfMemoryError異常。
參考資料
《深入理解Java虛擬機 第二版》
《Java虛擬機規(guī)范(Java SE7版)》
理解Java虛擬機體系結(jié)構(gòu)
目前主流的 Java 虛擬機有哪些?-知乎
jvm運行時數(shù)據(jù)區(qū)域解析
Java虛擬機原理圖解
深入探討 Java 類加載器