Java虛擬機(一)結(jié)構(gòu)原理與運行時數(shù)據(jù)區(qū)域

前言

本來計劃要寫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 類加載器

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末毙驯,一起剝皮案震驚了整個濱河市倒堕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌爆价,老刑警劉巖垦巴,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件媳搪,死亡現(xiàn)場離奇詭異,居然都是意外死亡骤宣,警方通過查閱死者的電腦和手機秦爆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來憔披,“玉大人等限,你說我怎么就攤上這事』钅妫” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵拗胜,是天一觀的道長蔗候。 經(jīng)常有香客問我,道長埂软,這世上最難降的妖魔是什么锈遥? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮勘畔,結(jié)果婚禮上所灸,老公的妹妹穿的比我還像新娘。我一直安慰自己炫七,他們只是感情好爬立,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布苟呐。 她就那樣靜靜地躺著膛檀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪镐侯。 梳的紋絲不亂的頭發(fā)上奕巍,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天吟策,我揣著相機與錄音,去河邊找鬼的止。 笑死檩坚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的诅福。 我是一名探鬼主播匾委,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼氓润!你這毒婦竟也來了剩檀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤旺芽,失蹤者是張志新(化名)和其女友劉穎沪猴,沒想到半個月后辐啄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡运嗜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年壶辜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片担租。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡砸民,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奋救,到底是詐尸還是另有隱情岭参,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布尝艘,位于F島的核電站演侯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏背亥。R本人自食惡果不足惜秒际,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望狡汉。 院中可真熱鬧娄徊,春花似錦、人聲如沸盾戴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尖啡。三九已至锐峭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間可婶,已是汗流浹背沿癞。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留矛渴,地道東北人椎扬。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像具温,于是被迫代替她去往敵國和親蚕涤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容