Java虛擬機(jī)底層原理詳細(xì)分析
Java虛擬機(jī)底層原理
Java虛擬機(jī)主要由以下三部分組成。
- 類(lèi)裝載子系統(tǒng):java代碼編譯成class文件后,首先由類(lèi)裝載子系統(tǒng)加載到虛擬機(jī)內(nèi)存中。
- 運(yùn)行時(shí)數(shù)據(jù)區(qū):就是俗稱(chēng)的虛擬機(jī)內(nèi)存,主要包括我們熟悉的堆、棧、本地方法棧、方法區(qū)(元空間)、程序計(jì)數(shù)器
- 字節(jié)碼執(zhí)行引擎:最終java代碼的真正執(zhí)行是由字節(jié)碼執(zhí)行引擎來(lái)執(zhí)行的。
虛擬機(jī)調(diào)優(yōu)主要針對(duì)的是運(yùn)行時(shí)數(shù)據(jù)區(qū),也就是虛擬機(jī)內(nèi)存。
一、堆
虛擬機(jī)調(diào)優(yōu)主要針對(duì)的就是堆。當(dāng)堆滿(mǎn)了的時(shí)候,字節(jié)碼執(zhí)行引擎就會(huì)單獨(dú)開(kāi)啟一個(gè)垃圾回收線(xiàn)程執(zhí)行g(shù)c。
二、棧
當(dāng)線(xiàn)程開(kāi)始執(zhí)行,java虛擬機(jī)立刻在棧中為該線(xiàn)程分配一塊內(nèi)存空間,這塊內(nèi)存是這個(gè)線(xiàn)程專(zhuān)屬的,主要放線(xiàn)程自己的局部變量,每個(gè)線(xiàn)程都有自己的內(nèi)存區(qū)域。
棧由一系列幀組成,一個(gè)方法對(duì)應(yīng)一塊棧幀內(nèi)存區(qū)域。幀保存一個(gè)方法的局部變量、操作數(shù)棧、常量池指針。每一次方法調(diào)用創(chuàng)建一個(gè)幀,并壓棧。
虛擬機(jī)內(nèi)存中的棧的數(shù)據(jù)結(jié)構(gòu)特性類(lèi)似數(shù)據(jù)結(jié)構(gòu)中的棧,滿(mǎn)足先進(jìn)后出(FILO)特性。當(dāng)一個(gè)方法運(yùn)行完,它對(duì)應(yīng)的棧幀就被銷(xiāo)毀。
雖然我們常說(shuō)棧幀是用來(lái)放局部變量的,但實(shí)際上要復(fù)雜的多。棧幀內(nèi)部主要包括:局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口四部分。下面以一段簡(jiǎn)單的代碼為例,介紹一下這幾部分的作用。
public int compute() { int a = 1; int b = 1; int c = (a + b) * 10; return c; }
java代碼編譯成的class文件打開(kāi)是十六進(jìn)制字符無(wú)法讀,需要使用javap -c進(jìn)行反編譯。以上代碼反編譯的結(jié)果如下:
可以通過(guò)JVM指令手冊(cè)查看每條命令的具體含義。
第0行iconst_1:將int類(lèi)型常量1壓入操作數(shù)棧。第1行istore_1:將int類(lèi)型存入局部變量1。這一步就是先在局部變量表分配一塊內(nèi)存給變量a,從操作數(shù)棧中的常量1取出賦給變量a并存入局部變量表。這兩行完成了源碼中的第一行 int a = 1; 。同理,第2行和第3行完成了源碼中的第二行Int b = 1; 。
第4行iload_1:從局部變量1裝載int類(lèi)型值。這里局部變量1就是a,這一步是把a(bǔ)中的值加載到操作數(shù)棧。第5行iload_2也是同樣道理,從局部變量2也就是b裝載int類(lèi)型值到操作數(shù)棧。第6行iadd:就是進(jìn)行加法運(yùn)算,java虛擬機(jī)進(jìn)行加減乘除運(yùn)算時(shí),會(huì)從操作數(shù)棧的棧頂彈出兩個(gè)元素進(jìn)行運(yùn)算。這里也就是iload_1和iload_2裝載的值。并且將運(yùn)算完成的結(jié)果壓回操作數(shù)棧。第7行bipush 10:將一個(gè)8位帶符號(hào)整數(shù)壓入操作數(shù)棧。這里就是后面的那個(gè)10。由于10也要占一個(gè)位置,所以這一條指令占了兩個(gè)位置,也就沒(méi)有了第8行。第9行imul:就是進(jìn)行乘法運(yùn)算,與第6行iadd類(lèi)似。第10行istore_3:將前面運(yùn)算表達(dá)式的結(jié)果從操作數(shù)棧中取出賦給變量c并存入局部變量表。從第4行到第10行完成了源碼中Int c = (a + b) * 10;這一行的操作。
第11行iload_1:從局部變量3裝載int類(lèi)型值。頁(yè)就是把局部變量c中的值取出加載到操作數(shù)棧。第12行ireturn:就很明顯了,就是返回結(jié)果。
通過(guò)上面簡(jiǎn)單程序的例子,應(yīng)該可以理解局部變量表和操作數(shù)棧的工作方式了吧。操作數(shù)棧簡(jiǎn)單的說(shuō)就是程序在運(yùn)行過(guò)程中,存放操作數(shù)的臨時(shí)內(nèi)存。局部變量如果是對(duì)象類(lèi)型,那么對(duì)象值實(shí)際是存在堆里的,而棧的局部變量表實(shí)際上只是保存該對(duì)象在堆內(nèi)存中的地址。
棧幀中除了局部變量表和操作數(shù)棧還有動(dòng)態(tài)連接和方法出口。
每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,持有這個(gè)引用是為了方法調(diào)用過(guò)程中的動(dòng)態(tài)連接。Class文件中的常量池中存有大量的符號(hào)引用,字節(jié)碼中的方法調(diào)用指令就是以常量池里指向方法的符號(hào)引用作為參數(shù)的,這些符號(hào)的一部分會(huì)在類(lèi)加載階段或者第一次使用的時(shí)候被轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化被稱(chēng)為靜態(tài)解析。另一部分在每一次運(yùn)行期間轉(zhuǎn)化為直接引用,被稱(chēng)為動(dòng)態(tài)連接。因?yàn)榉椒ǖ膱?zhí)行對(duì)應(yīng)著棧幀的入棧出棧,所以將所屬方法的引用存放在棧幀中。
方法出口記錄了上級(jí)調(diào)用方法的信息,比如compute()方法是main()方法調(diào)用的。當(dāng)compute()方法執(zhí)行完成返回時(shí),需要返回到上級(jí)調(diào)用方法main()繼續(xù)運(yùn)行,此時(shí)需要知道一些調(diào)用方法main()的信息,比如返回到main()的哪個(gè)位置。方法出口就記錄了相關(guān)信息。
三、程序計(jì)數(shù)器
程序計(jì)數(shù)器也是屬于線(xiàn)程的,標(biāo)識(shí)這個(gè)線(xiàn)程正在或者馬上要執(zhí)行的那行程序的行號(hào)。如上例中0-12就是行號(hào),程序運(yùn)行過(guò)程中,字節(jié)碼執(zhí)行引擎每執(zhí)行一行代碼,都會(huì)修改程序計(jì)數(shù)器的值。
為什么需要程序計(jì)數(shù)器呢?如果當(dāng)前線(xiàn)程掛起了,讓出了cpu去執(zhí)行其它線(xiàn)程,當(dāng)線(xiàn)程重新獲得cpu時(shí)間片開(kāi)始執(zhí)行時(shí),需要讓線(xiàn)程知道應(yīng)該執(zhí)行到哪一行了,而程序計(jì)數(shù)器則記錄了要執(zhí)行程序的行號(hào)。
四、方法區(qū)(元空間)
class文件通過(guò)類(lèi)裝載子系統(tǒng)加載到虛擬機(jī)內(nèi)存中,其實(shí)主要就是加載到方法區(qū)。方法區(qū)內(nèi)主要是常量、靜態(tài)變量、類(lèi)元信息等。類(lèi)元信息就是類(lèi)的名稱(chēng)、修飾符、方法簽名、方法中的指令碼等。創(chuàng)建對(duì)象時(shí),在對(duì)象頭中會(huì)保存指向類(lèi)元信息的地址指針。
JDK1.8之后改叫元空間,也不再使用虛擬機(jī)內(nèi)存,而是使用直接內(nèi)存??梢允褂?XX:MetaspaceSize和-XX:MaxMetaspaceSize設(shè)置元空間初始大小以及最大可分配大小。
1.如果不指定元空間的大小,默認(rèn)情況下,元空間最大的大小是系統(tǒng)內(nèi)存的大小,如果元空間一直擴(kuò)大,虛擬機(jī)可能會(huì)消耗完所有的可用系統(tǒng)內(nèi)存。
2.如果元空間內(nèi)存不夠用,就會(huì)報(bào)OOM。
3.默認(rèn)情況下,對(duì)應(yīng)一個(gè)64位的服務(wù)端JVM來(lái)說(shuō),其默認(rèn)的-XX:MetaspaceSize值為21MB,這就是初始的高水位線(xiàn),一旦元空間的大小觸及這個(gè)高水位線(xiàn),就會(huì)觸發(fā)Full GC并會(huì)卸載沒(méi)有用的類(lèi),然后高水位線(xiàn)的值將會(huì)被重置。
4.從第3點(diǎn)可以知道,如果初始化的高水位線(xiàn)設(shè)置過(guò)低,會(huì)頻繁的觸發(fā)Full GC,高水位線(xiàn)會(huì)被多次調(diào)整。所以為了避免頻繁GC以及調(diào)整高水位線(xiàn),建議將-XX:MetaspaceSize設(shè)置為較高的值,而-XX:MaxMetaspaceSize不進(jìn)行設(shè)置。
到此這篇關(guān)于Java虛擬機(jī)底層原理詳細(xì)分析的文章就介紹到這了,更多相關(guān)Java虛擬機(jī)底層原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Data?JPA?映射VO/DTO對(duì)象方式
這篇文章主要介紹了Spring?Data?JPA?映射VO/DTO對(duì)象方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11SpringBoot整合Mybatis注解開(kāi)發(fā)的實(shí)現(xiàn)代碼
這篇文章主要介紹了SpringBoot整合Mybatis注解開(kāi)發(fā)的實(shí)現(xiàn)代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11java 中接口和抽象類(lèi)的區(qū)別與對(duì)比
這篇文章主要介紹了java 中接口和抽象類(lèi)的區(qū)別與對(duì)比的相關(guān)資料,這里詳細(xì)說(shuō)明他們之家的區(qū)別,需要的朋友可以參考下2017-08-08Java實(shí)現(xiàn)從字符串中找出數(shù)字字符串的方法小結(jié)
這篇文章主要介紹了Java實(shí)現(xiàn)從字符串中找出數(shù)字字符串的方法,結(jié)合實(shí)例形式總結(jié)分析了Java查找數(shù)字字符串的常用技巧,需要的朋友可以參考下2016-03-03IntelliJ IDEA的build path設(shè)置方法
這篇文章主要介紹了IntelliJ IDEA的build path設(shè)置方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04