Java基礎(chǔ):Java虛擬機(jī)(JVM)

Java基礎(chǔ):Java虛擬機(jī)(JVM)

當(dāng)我們第一次學(xué)習(xí)Java時(shí)這些原理上的東西就會(huì)被提到,但是很少有真正去學(xué)習(xí)偷厦。今天開始從頭過一遍Java霹肝,打算從JVM開始申钩。

1. JVM是什么

2. JRE和JDK

3. JVM結(jié)構(gòu)

3.1. 程序計(jì)數(shù)器(PC, Program Counter)

3.2. Java虛擬機(jī)棧(Stack,Java Virtual Mechine Stacks)

3.3. 本地方法棧(Native Stack)

3.4. Java 堆(Heap, Garbage Collection Heap)

3.5. 方法區(qū)(Method Area)

3.6. 代碼緩存(Code Cache)

3.7. 類信息(Class Data)

3.8. 運(yùn)行時(shí)常量池(Run-Time Constant Pool)

3.9. 直接內(nèi)存(Direct Memory)

4. Java垃圾回收

5. JVM線程與原生線程的關(guān)系

6. 參考文章

1. JVM是什么

JVM是Java Virtual Mechine的縮寫痹升。它是一種基于計(jì)算設(shè)備的規(guī)范建炫,是一臺(tái)虛擬機(jī),即虛構(gòu)的計(jì)算機(jī)疼蛾。

JVM屏蔽了具體操作系統(tǒng)平臺(tái)的信息(顯然,就像是我們?cè)陔娔X上開了個(gè)虛擬機(jī)一樣)艺配,當(dāng)然察郁,JVM執(zhí)行字節(jié)碼時(shí)實(shí)際上還是要解釋成具體操作平臺(tái)的機(jī)器指令的。

通過JVM转唉,Java實(shí)現(xiàn)了平臺(tái)無關(guān)性皮钠,Java語言在不同平臺(tái)運(yùn)行時(shí)不需要重新編譯,只需要在該平臺(tái)上部署JVM就可以了赠法。因而能實(shí)現(xiàn)一次編譯多處運(yùn)行麦轰。(就像是你的虛擬機(jī)也可以在任何安了VMWare的系統(tǒng)上運(yùn)行)

2. JRE和JDK

JRE:Java Runtime Environment,也就是JVM的運(yùn)行平臺(tái)砖织,聯(lián)系平時(shí)用的虛擬機(jī)款侵,大概可以理解成JRE=虛擬機(jī)平臺(tái)+虛擬機(jī)本體(JVM)。類似于你電腦上的VMWare+適用于VMWare的Ubuntu虛擬機(jī)侧纯。這樣我們也就明白了JVM到底是個(gè)什么新锈。

JDK:Java Develop Kit,Java的開發(fā)工具包眶熬,JDK本體也是Java程序妹笆,因此運(yùn)行依賴于JRE,由于需要保持JDK的獨(dú)立性與完整性,JDK的安裝目錄下通常也附有JRE娜氏。目前Oracle提供的Windows下的JDK安裝工具會(huì)同時(shí)安裝一個(gè)正常的JRE和隸屬于JDK目錄下的JRE拳缠。

3. JVM結(jié)構(gòu)

JVM主要包括:程序計(jì)數(shù)器(Program Counter),Java堆(Heap)贸弥,Java虛擬機(jī)棧(Stack)窟坐,本地方法棧(Native Stack),方法區(qū)(Method Area)

詳細(xì)的結(jié)構(gòu)如下:

現(xiàn)在我來分別介紹一下每一部分的功能茂腥。

3.1. 程序計(jì)數(shù)器(PC, Program Counter)

是一個(gè)寄存器狸涌,可以看作是代碼行號(hào)指示器,類似于實(shí)際計(jì)算機(jī)里的PC最岗,用于指示帕胆,跳轉(zhuǎn)下一條需要執(zhí)行的命令。Java的基礎(chǔ)操作以及異常處理等都十分依賴PC般渡。

JVM多線程是通過線程輪流切換并分配處理器執(zhí)行時(shí)間的方式來實(shí)現(xiàn)的懒豹。在一個(gè)確定的時(shí)刻芙盘,一個(gè)處理器(或者說多核處理器的一個(gè)內(nèi)核)只會(huì)執(zhí)行一條線程中的命令。因此脸秽,為了正常的切換線程儒老,每個(gè)線程都會(huì)有一個(gè)獨(dú)立的PC,各線程的PC不會(huì)互相影響记餐。這個(gè)私有的PC所占的這塊內(nèi)存即是線程的“私有內(nèi)存”驮樊。

如果線程在執(zhí)行的是Java方法,那么PC記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址片酝。如果正在執(zhí)行的不是Java方法即Native方法囚衔,那么PC的值為undefined。

PC的內(nèi)存區(qū)域是唯一的沒有規(guī)定任何OutOfMemoryError的Java虛擬機(jī)規(guī)范中的區(qū)域雕沿。

3.2. Java虛擬機(jī)棧(Stack练湿,Java Virtual Mechine Stacks)

同PC一樣(從工作流程圖里我們可以看到,實(shí)際上审轮,PC也是存在于JVM Stack上的)肥哎,也是線程私有的,生命周期與線程相同疾渣。虛擬機(jī)棧描述Java方法執(zhí)行的內(nèi)存模型篡诽,每個(gè)方法被執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame),棧幀會(huì)利用局部變量數(shù)組存儲(chǔ)局部變量(Local Variables)稳衬,操作棧(Operand Stack)霞捡,方法出口(Return Value),動(dòng)態(tài)連接(Current Class Constant Pool Reference)等信息薄疚。

局部變量數(shù)組存儲(chǔ)了編譯可知的八個(gè)基本類型(int, boolean, char, short, byte, long, float, double)碧信,對(duì)象引用(根據(jù)不同的虛擬機(jī)實(shí)現(xiàn)可能是引用地址的指針或者一個(gè)handle),returnAddress類型街夭。64位的long和double會(huì)占用兩個(gè)Slot砰碴,其余類型會(huì)占用一個(gè)Slot。在編譯期間板丽,局部變量所需的空間就會(huì)完成分配呈枉,動(dòng)態(tài)運(yùn)行期間不會(huì)改變所需的空間。

操作棧在執(zhí)行字節(jié)碼指令時(shí)會(huì)被用到埃碱,這種方式類似于原生的CPU寄存器猖辫,大部分JVM把時(shí)間花費(fèi)在操作棧的花費(fèi)上,操作棧和局部變量數(shù)組會(huì)頻繁的交換數(shù)據(jù)砚殿。

動(dòng)態(tài)連接控制著運(yùn)行時(shí)常量池和棧幀的連接啃憎。所有方法和類的引用都會(huì)被當(dāng)作符號(hào)的引用存在常量池中。符號(hào)引用是實(shí)際上并不指向物理內(nèi)存地址的邏輯引用似炎。JVM 可以選擇符號(hào)引用解析的時(shí)機(jī)辛萍,一種是當(dāng)類文件加載并校驗(yàn)通過后悯姊,這種解析方式被稱為饑餓方式。另外一種是符號(hào)引用在第一次使用的時(shí)候被解析贩毕,這種解析方式稱為惰性方式悯许。無論如何 ,JVM 必須要在第一次使用符號(hào)引用時(shí)完成解析并拋出可能發(fā)生的解析錯(cuò)誤辉阶。綁定是將對(duì)象域先壕、方法、類的符號(hào)引用替換為直接引用的過程睛藻。綁定只會(huì)發(fā)生一次启上。一旦綁定,符號(hào)引用會(huì)被完全替換店印。如果一個(gè)類的符號(hào)引用還沒有被解析,那么就會(huì)載入這個(gè)類倒慧。每個(gè)直接引用都被存儲(chǔ)為相對(duì)于存儲(chǔ)結(jié)構(gòu)(與運(yùn)行時(shí)變量或方法的位置相關(guān)聯(lián)的)偏移量按摘。

對(duì)Java虛擬機(jī)棧這個(gè)區(qū)域,Java虛擬機(jī)規(guī)范規(guī)定了兩種異常:

線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度纫谅,拋出StackOverFlow異常炫贤。

對(duì)于支持動(dòng)態(tài)擴(kuò)展的虛擬機(jī),當(dāng)擴(kuò)展無法申請(qǐng)到足夠的內(nèi)存時(shí)會(huì)拋出OutOfMemory異常付秕。

3.3. 本地方法棧(Native Stack)

本地方法棧如其名字兰珍,和Java Virtual Machine Stack其實(shí)極為類似,只是執(zhí)行的是Native方法询吴,為Native方法服務(wù)掠河。在JVM規(guī)范中,沒有對(duì)它的實(shí)現(xiàn)做具體規(guī)定猛计。

3.4. Java 堆(Heap, Garbage Collection Heap)

Java堆是被所有線程共享的一塊區(qū)域唠摹,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例奉瘤,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存(隨著技術(shù)的發(fā)展勾拉,已不絕對(duì))。

Java堆是垃圾收集器管理的主要區(qū)域盗温,因而也被稱為GC堆藕赞。收集器采用分代回收法,GC堆可以分為新生代(Yong Generation)和老生代(Old Generation)卖局。新生代包括Eden Space和Survivor Space斧蜕。但無論哪個(gè)區(qū)域,如何劃分吼驶,存儲(chǔ)的都是Java對(duì)象實(shí)例惩激,進(jìn)一步的劃分是為了更好的回收內(nèi)存或快速的分配內(nèi)存店煞。

根據(jù)Java虛擬機(jī)規(guī)范,堆所在的物理內(nèi)存區(qū)間可以是不連續(xù)的风钻,只要邏輯連續(xù)就可以顷蟀。實(shí)現(xiàn)時(shí)既可以是固定大小,也可以是可擴(kuò)展的骡技。如果堆無法擴(kuò)展時(shí)鸣个,就會(huì)拋出OutOfMemoryError。

3.5. 方法區(qū)(Method Area)

方法區(qū)和Java堆類似布朦,也屬于各線程共享的內(nèi)存區(qū)域囤萤。用于存儲(chǔ)已被虛擬機(jī)加載的類信息,常量是趴,靜態(tài)變量涛舍,即時(shí)編譯器編譯后的代碼數(shù)據(jù)等。它屬于非堆區(qū)(Non Heap)唆途,和Java堆區(qū)分開富雅。對(duì)于存在永久代(Permanent)概念的虛擬機(jī)(HotSpot)而言,方法區(qū)存在于永久代肛搬。Java虛擬機(jī)規(guī)范對(duì)方法區(qū)的規(guī)定很寬松没佑,甚至可以不實(shí)現(xiàn)GC。不過并非進(jìn)入方法區(qū)的數(shù)據(jù)就會(huì)永久存在了温赔,這塊區(qū)域的內(nèi)存回收主要為常量池的回收和類型的卸載蛤奢。這個(gè)區(qū)域的回收處理不善也會(huì)導(dǎo)致嚴(yán)重的內(nèi)存泄漏。

當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時(shí)也會(huì)拋出OutOfMemoryError陶贼。

3.6. 代碼緩存(Code Cache)

用于編譯和存儲(chǔ)那些被 JIT 編譯器編譯成原生代碼的方法啤贩。

3.7. 類信息(Class Data)

類信息存儲(chǔ)在方法區(qū),其主要構(gòu)成為運(yùn)行時(shí)常量池(Run-Time Constant Pool)和方法(Method Code)骇窍。

一個(gè)編譯后的類文件包括以下結(jié)構(gòu):

結(jié)構(gòu)解釋

magic, minor_version, major_version類文件的版本信息和用于編譯這個(gè)類的 JDK 版本瓜晤。

constant_pool類似于符號(hào)表,盡管它包含更多數(shù)據(jù)腹纳。下面有更多的詳細(xì)描述痢掠。

access_flags提供這個(gè)類的描述符列表。

this_class提供這個(gè)類全名的常量池(constant_pool)索引嘲恍,比如org/jamesdbloom/foo/Bar足画。

super_class提供這個(gè)類的父類符號(hào)引用的常量池索引。

interfaces指向常量池的索引數(shù)組佃牛,提供那些被實(shí)現(xiàn)的接口的符號(hào)引用淹辞。

fields提供每個(gè)字段完整描述的常量池索引數(shù)組。

methods指向constant_pool的索引數(shù)組俘侠,用于表示每個(gè)方法簽名的完整描述象缀。如果這個(gè)方法不是抽象方法也不是 native 方法蔬将,那么就會(huì)顯示這個(gè)函數(shù)的字節(jié)碼。

attributes不同值的數(shù)組央星,表示這個(gè)類的附加信息霞怀,包括 RetentionPolicy.CLASS 和 RetentionPolicy.RUNTIME 注解。

3.8. 運(yùn)行時(shí)常量池(Run-Time Constant Pool)

運(yùn)行時(shí)常量池是方法區(qū)的一部分莉给。Class文件中有類的版本毙石,字段,方法颓遏,接口等描述信息和用于存放編譯期生成的各種字面量和符號(hào)引用徐矩。這部分內(nèi)容將在類加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中。Java虛擬機(jī)規(guī)范對(duì)Class的細(xì)節(jié)有著嚴(yán)苛的要求而對(duì)運(yùn)行時(shí)常量池的實(shí)現(xiàn)不做要求叁幢。一般來說除了翻譯的Class,翻譯出來的直接引用也會(huì)存在運(yùn)行時(shí)常量池中滤灯。

運(yùn)行時(shí)常量池具備動(dòng)態(tài)性,即運(yùn)行時(shí)也可將新的常量放入池中曼玩。比如String類的intern()方法力喷。

常量池?zé)o法申請(qǐng)到足夠的內(nèi)存分配時(shí)也會(huì)拋出OutOfMemoryError。

3.9. 直接內(nèi)存(Direct Memory)

直接內(nèi)存并不在Java虛擬機(jī)規(guī)范中演训,不是Java的一部分,但是也被頻繁使用并可能導(dǎo)致OutOfMemoryError贝咙。Native函數(shù)庫可以直接分配堆外內(nèi)存样悟,通過存儲(chǔ)在Java堆里的DirectDataBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。這樣做在一些場(chǎng)景中可以顯著提高性能庭猩。

直接內(nèi)存是堆外內(nèi)存窟她,自然不受Java堆大小的限制,但是可能受實(shí)體機(jī)內(nèi)存大小的限制蔼水。如果內(nèi)存各部分總和大于實(shí)體機(jī)的內(nèi)存時(shí)震糖,也會(huì)報(bào)出OutOfMemoryError。

4. Java垃圾回收

將內(nèi)存中不再被使用的對(duì)象進(jìn)行回收趴腋,GC中用于回收的方法稱為收集器吊说,由于GC需要消耗一些資源和時(shí)間,Java在對(duì)對(duì)象的生命周期特征進(jìn)行分析后优炬,按照新生代颁井、舊生代的方式來對(duì)對(duì)象進(jìn)行收集,以盡可能的縮短GC對(duì)應(yīng)用造成的暫停蠢护。

不同的對(duì)象引用類型雅宾, GC會(huì)采用不同的方法進(jìn)行回收,JVM對(duì)象的引用分為了四種類型:

強(qiáng)引用:默認(rèn)情況下葵硕,對(duì)象采用的均為強(qiáng)引用(這個(gè)對(duì)象的實(shí)例沒有其他對(duì)象引用眉抬,GC時(shí)才會(huì)被回收)贯吓。

軟引用:軟引用是Java中提供的一種比較適合于緩存場(chǎng)景的應(yīng)用(只有在內(nèi)存不夠用的情況下才會(huì)被GC)。

弱引用:在GC時(shí)一定會(huì)被GC回收蜀变。

虛引用:由于虛引用只是用來得知對(duì)象是否被GC悄谐。

5. JVM線程與原生線程的關(guān)系

JVM允許一個(gè)程序使用多個(gè)并發(fā)線程,Hotspot JVM中Java的線程與原生操作系統(tǒng)的線程是直接映射關(guān)系昏苏。即當(dāng)線程本地存儲(chǔ)尊沸、緩沖區(qū)分配、同步對(duì)象贤惯、棧洼专、程序計(jì)數(shù)器等準(zhǔn)備好以后,就會(huì)創(chuàng)建一個(gè)操作系統(tǒng)原生線程孵构。Java 線程結(jié)束屁商,原生線程隨之被回收。操作系統(tǒng)負(fù)責(zé)調(diào)度所有線程颈墅,并把它們分配到任何可用的 CPU 上蜡镶。當(dāng)原生線程初始化完畢,就會(huì)調(diào)用 Java 線程的 run() 方法恤筛。run() 返回時(shí)官还,被處理未捕獲異常,原生線程將確認(rèn)由于它的結(jié)束是否要終止 JVM 進(jìn)程(比如這個(gè)線程是最后一個(gè)非守護(hù)線程)毒坛。當(dāng)線程結(jié)束時(shí)望伦,會(huì)釋放原生線程和 Java 線程的所有資源。

6. 參考文章

深入理解JVM——JVM內(nèi)存模式

JVM內(nèi)幕——Java虛擬機(jī)詳解

JVM介紹

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末煎殷,一起剝皮案震驚了整個(gè)濱河市屯伞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌豪直,老刑警劉巖劣摇,帶你破解...
    沈念sama閱讀 223,126評(píng)論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異弓乙,居然都是意外死亡末融,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門唆貌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滑潘,“玉大人,你說我怎么就攤上這事锨咙∮锫保” “怎么了?”我有些...
    開封第一講書人閱讀 169,941評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)粹舵。 經(jīng)常有香客問我钮孵,道長(zhǎng),這世上最難降的妖魔是什么眼滤? 我笑而不...
    開封第一講書人閱讀 60,294評(píng)論 1 300
  • 正文 為了忘掉前任巴席,我火速辦了婚禮,結(jié)果婚禮上诅需,老公的妹妹穿的比我還像新娘漾唉。我一直安慰自己,他們只是感情好堰塌,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,295評(píng)論 6 398
  • 文/花漫 我一把揭開白布赵刑。 她就那樣靜靜地躺著,像睡著了一般场刑。 火紅的嫁衣襯著肌膚如雪般此。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,874評(píng)論 1 314
  • 那天牵现,我揣著相機(jī)與錄音铐懊,去河邊找鬼。 笑死瞎疼,一個(gè)胖子當(dāng)著我的面吹牛科乎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贼急,決...
    沈念sama閱讀 41,285評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼喜喂,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了竿裂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,249評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤照弥,失蹤者是張志新(化名)和其女友劉穎腻异,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體这揣,經(jīng)...
    沈念sama閱讀 46,760評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悔常,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,840評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了给赞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片机打。...
    茶點(diǎn)故事閱讀 40,973評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖片迅,靈堂內(nèi)的尸體忽然破棺而出残邀,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,631評(píng)論 5 351
  • 正文 年R本政府宣布芥挣,位于F島的核電站驱闷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏空免。R本人自食惡果不足惜空另,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,315評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蹋砚。 院中可真熱鬧扼菠,春花似錦、人聲如沸坝咐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽畅厢。三九已至冯痢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間框杜,已是汗流浹背浦楣。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評(píng)論 1 275
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咪辱,地道東北人振劳。 一個(gè)月前我還...
    沈念sama閱讀 49,431評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像油狂,于是被迫代替她去往敵國(guó)和親历恐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,982評(píng)論 2 361

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