怎么理解java虛擬機(jī)執(zhí)行子系統(tǒng)

本篇內(nèi)容主要講解“怎么理解java虛擬機(jī)執(zhí)行子系統(tǒng)”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“怎么理解java虛擬機(jī)執(zhí)行子系統(tǒng)”吧!

公司主營(yíng)業(yè)務(wù):成都網(wǎng)站制作、網(wǎng)站建設(shè)、移動(dòng)網(wǎng)站開(kāi)發(fā)等業(yè)務(wù)。幫助企業(yè)客戶(hù)真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競(jìng)爭(zhēng)能力。創(chuàng)新互聯(lián)是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開(kāi)放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來(lái)的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶(hù)帶來(lái)驚喜。創(chuàng)新互聯(lián)推出盈江免費(fèi)做網(wǎng)站回饋大家。

類(lèi)文件結(jié)構(gòu)

各種不同平臺(tái)的虛擬機(jī)與所有平臺(tái)都統(tǒng)一使用的程序存儲(chǔ)格式— 字節(jié)碼( ByteCode ) 是構(gòu)成平臺(tái)無(wú)關(guān)性的基石

                                        怎么理解java虛擬機(jī)執(zhí)行子系統(tǒng)

                                                            圖-Java虛擬機(jī)提供的語(yǔ)言無(wú)關(guān)性

虛擬機(jī)類(lèi)加載機(jī)制

虛擬機(jī)把描述類(lèi)的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類(lèi)型,這就是虛擬機(jī)的類(lèi)加載機(jī)制。

類(lèi)加載的時(shí)機(jī)

Java語(yǔ)言中類(lèi)型的加載、連接以及初始化過(guò)程都是在程序運(yùn)行期間完成的,這種策略雖然會(huì)使類(lèi)加載時(shí)稍微增加一些性能開(kāi)銷(xiāo),但是會(huì)為Java應(yīng)用程序提供高度的靈活性。Java里天生就可以動(dòng)態(tài)擴(kuò)展語(yǔ)言特性就是依賴(lài)運(yùn)行期間動(dòng)態(tài)加載和動(dòng)態(tài)連接這個(gè)特點(diǎn)實(shí)現(xiàn)的。比如,如果編寫(xiě)一個(gè)面向接口的程序,可以等到運(yùn)行時(shí)再指定其具體實(shí)現(xiàn)類(lèi);用戶(hù)可以通過(guò)Java預(yù)定義的和自定義類(lèi)加載器,讓一個(gè)本地的應(yīng)用程序可以在運(yùn)行時(shí)從網(wǎng)絡(luò)或其它地方加載一個(gè)二進(jìn)制流作為程序代碼的一部分,這種組裝應(yīng)用程序的方式目前已廣泛應(yīng)用于Java程序之中。從最基礎(chǔ)的JSP到相對(duì)復(fù)雜的OSGI技術(shù),都使用了Java語(yǔ)言運(yùn)行類(lèi)加載的特性。

類(lèi)從被加載到虛擬機(jī)內(nèi)存中開(kāi)始,到卸載出內(nèi)存為止,它的整個(gè)生命周期包括:加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7個(gè)階段。其中驗(yàn)證、準(zhǔn)備、解析3個(gè)部分統(tǒng)稱(chēng)為連接(Linking),這7個(gè)階段的發(fā)生順序如圖:

                            怎么理解java虛擬機(jī)執(zhí)行子系統(tǒng)

                                                        圖-類(lèi)的生命周期

加載、驗(yàn)證、準(zhǔn)備、初始化和卸載這5個(gè)階段的順序是確定的,類(lèi)的加載過(guò)程必須按照這種順序按部就班地開(kāi)始,而解析階段則不一定:它在某些情況下可以在初始化階段之后再開(kāi)始,這是為了支持Java語(yǔ)言的運(yùn)行時(shí)綁定(也稱(chēng)為動(dòng)態(tài)綁定或晚期綁定)。
什么情況下需要開(kāi)始類(lèi)加載過(guò)程的第一個(gè)階段:加載?Java虛擬機(jī)規(guī)范中并沒(méi)有進(jìn)行強(qiáng)制約束,這點(diǎn)可以交給虛擬機(jī)的具體實(shí)現(xiàn)來(lái)自由把握。但是對(duì)于初始化階段,虛擬機(jī)規(guī)范則是嚴(yán)格規(guī)定了有且只有5種情況必須立即對(duì)類(lèi)進(jìn)行“初始化”(而加載、驗(yàn)證、準(zhǔn)備自然需要在此之前開(kāi)始):
1)遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。生成這4條指令的最常見(jiàn)的Java代碼場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候、讀取或設(shè)置一個(gè)類(lèi)的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候,以及調(diào)用一個(gè)類(lèi)的靜態(tài)方法的時(shí)候。
2)使用java.lang.reflect包的方法對(duì)類(lèi)進(jìn)行反射調(diào)用的時(shí)候,如果類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。
3)當(dāng)初始化一個(gè)類(lèi)的時(shí)候,如果發(fā)現(xiàn)其父類(lèi)還沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其父類(lèi)的初始化。
4)當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶(hù)需要指定一個(gè)要執(zhí)行的主類(lèi)(包含main()方法的那個(gè)類(lèi)),虛擬機(jī)會(huì)先初始化這個(gè)主類(lèi)。
5)當(dāng)使用JDK 1.7的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。

類(lèi)加載的過(guò)程

加載

“加載”是“類(lèi)加載”(Class Loading)過(guò)程的一個(gè)階段。在加載階段,虛擬機(jī)需要完成以下3件事情
1)通過(guò)一個(gè)類(lèi)的全限定名來(lái)獲取定義此類(lèi)的二進(jìn)制字節(jié)流。
2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
3)在內(nèi)存中生成一個(gè)代表這個(gè)類(lèi)的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類(lèi)的各種數(shù)據(jù)的訪(fǎng)問(wèn)入口。

通過(guò)類(lèi)型的完全限定名,產(chǎn)生一個(gè)代表該類(lèi)型的二進(jìn)制數(shù)據(jù)流的幾種常見(jiàn)形式:
1)從zip包中讀取,成為日后JAR、EAR、WAR格式的基礎(chǔ);
2)從網(wǎng)絡(luò)中獲取,這種場(chǎng)景最典型的應(yīng)用就是Applet;
3)運(yùn)行時(shí)計(jì)算生成,這種場(chǎng)景最常用的就是動(dòng)態(tài)代理技術(shù)了;
4)由其他文件生成,比如我們的JSP;
相對(duì)于類(lèi)加載過(guò)程的其他階段,一個(gè)非數(shù)組類(lèi)(數(shù)組類(lèi)比較特殊,有虛擬機(jī)直接創(chuàng)建的)的加載階段(準(zhǔn)確地說(shuō),是加載階段中獲取類(lèi)的二進(jìn)制字節(jié)流的動(dòng)作)是開(kāi)發(fā)人員可控性最強(qiáng)的,因?yàn)榧虞d階段既可以使用系統(tǒng)提供的引導(dǎo)類(lèi)加載器來(lái)完成,也可以由用戶(hù)自定義的類(lèi)加載器去完成,開(kāi)發(fā)人員可以通過(guò)定義自己的類(lèi)加載器去控制字節(jié)流的獲取方式(即重寫(xiě)一個(gè)類(lèi)加載器的loadClass()方法)。
加載階段完成后,虛擬機(jī)外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲(chǔ)在方法區(qū)之中,方法區(qū)中的數(shù)據(jù)存儲(chǔ)格式由虛擬機(jī)實(shí)現(xiàn)自行定義,虛擬機(jī)規(guī)范未規(guī)定此區(qū)域的具體數(shù)據(jù)結(jié)構(gòu)。然后在內(nèi)存中實(shí)例化一個(gè)java.lang.Class類(lèi)的對(duì)象(并沒(méi)有明確規(guī)定是在Java堆中,對(duì)于HotSpot虛擬機(jī)而言,Class對(duì)象比較特殊,它雖然是對(duì)象,但是存放在方法區(qū)里面),這個(gè)對(duì)象將作為程序訪(fǎng)問(wèn)方法區(qū)中的這些類(lèi)型數(shù)據(jù)的外部接口。

驗(yàn)證

驗(yàn)證是鏈接階段的第一步,這一步主要的目的是確保class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身安全。 
驗(yàn)證階段主要包括四個(gè)檢驗(yàn)過(guò)程:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證和符號(hào)引用驗(yàn)證。 
1.文件格式驗(yàn)證
驗(yàn)證class文件格式規(guī)范,例如class文件是否已魔術(shù)0xCAFEBABE開(kāi)頭 , 主、次版本號(hào)是否在當(dāng)前虛擬機(jī)處理范圍之內(nèi)等。
2.元數(shù)據(jù)驗(yàn)證
這個(gè)階段是對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析,以保證其描述的信息符合java語(yǔ)言規(guī)范要求。驗(yàn)證點(diǎn)可能包括:這個(gè)類(lèi)是否有父類(lèi)(除了java.lang.Object之外,所有的類(lèi)都應(yīng)當(dāng)有父類(lèi))、這個(gè)類(lèi)是否繼承了不允許被繼承的類(lèi)(被final修飾的)、如果這個(gè)類(lèi)的父類(lèi)是抽象類(lèi),是否實(shí)現(xiàn)了起父類(lèi)或接口中要求實(shí)現(xiàn)的所有方法。
3.字節(jié)碼驗(yàn)證
進(jìn)行數(shù)據(jù)流和控制流分析,這個(gè)階段對(duì)類(lèi)的方法體進(jìn)行校驗(yàn)分析,這個(gè)階段的任務(wù)是保證被校驗(yàn)類(lèi)的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的行為。如:保證訪(fǎng)法體中的類(lèi)型轉(zhuǎn)換有效,例如可以把一個(gè)子類(lèi)對(duì)象賦值給父類(lèi)數(shù)據(jù)類(lèi)型,這是安全的,但不能把一個(gè)父類(lèi)對(duì)象賦值給子類(lèi)數(shù)據(jù)類(lèi)型、保證跳轉(zhuǎn)命令不會(huì)跳轉(zhuǎn)到方法體以外的字節(jié)碼命令上。
4.符號(hào)引用驗(yàn)證
對(duì)類(lèi)自身以外(常量池中的各種符號(hào)引用)的信息進(jìn)行匹配性校驗(yàn)

準(zhǔn)備

準(zhǔn)備階段是正式為類(lèi)變量分配內(nèi)存并設(shè)置類(lèi)變量初始值的階段,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配。這個(gè)階段中有兩個(gè)容易產(chǎn)生混淆的知識(shí)點(diǎn),首先是這時(shí)候進(jìn)行內(nèi)存分配的僅包括類(lèi)變量(static 修飾的變量),而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在java堆中。其次是這里所說(shuō)的初始值“通常情況”下是數(shù)據(jù)類(lèi)型的零值,假設(shè)一個(gè)類(lèi)變量定義為: 
public static int value = 12;
那么變量value在準(zhǔn)備階段過(guò)后的初始值為0而不是12,因?yàn)檫@時(shí)候尚未開(kāi)始執(zhí)行任何java方法,而把value賦值為123的putstatic指令是程序被編譯后,存放于類(lèi)構(gòu)造器()方法之中,所以把value賦值為12的動(dòng)作將在初始化階段才會(huì)被執(zhí)行。
上面所說(shuō)的“通常情況”下初始值是零值,那相對(duì)于一些特殊的情況,如果類(lèi)字段的字段屬性表中存在ConstantValue屬性,那在準(zhǔn)備階段變量value就會(huì)被初始化為ConstantValue屬性所指定的值,建設(shè)上面類(lèi)變量value定義為: 
public static finalint value = 123;
編譯時(shí)javac將會(huì)為value生成ConstantValue屬性,在準(zhǔn)備階段虛擬機(jī)就會(huì)根據(jù)ConstantValue的設(shè)置將value設(shè)置為123。

解析

解析階段是虛擬機(jī)常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程。 
符號(hào)引用:符號(hào)引用是一組符號(hào)來(lái)描述所引用的目標(biāo)對(duì)象,符號(hào)可以是任何形式的字面量,只要使用時(shí)能無(wú)歧義地定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)對(duì)象并不一定已經(jīng)加載到內(nèi)存中。 
直接引用:直接引用可以是直接指向目標(biāo)對(duì)象的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。直接引用是與虛擬機(jī)內(nèi)存布局實(shí)現(xiàn)相關(guān)的,同一個(gè)符號(hào)引用在不同虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一般不會(huì)相同,如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。

初始化

類(lèi)的初始化階段是類(lèi)加載過(guò)程的最后一步,在準(zhǔn)備階段,類(lèi)變量已賦過(guò)一次系統(tǒng)要求的初始值,而在初始化階段,則是根據(jù)程序員通過(guò)程序制定的主觀(guān)計(jì)劃去初始化類(lèi)變量和其他資源,或者可以從另外一個(gè)角度來(lái)表達(dá):初始化階段是執(zhí)行類(lèi)構(gòu)造器<clinit>()方法的過(guò)程。在以下四種情況下初始化過(guò)程會(huì)被觸發(fā)執(zhí)行:
1.遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則需先觸發(fā)其初始化。生成這4條指令的最常見(jiàn)的java代碼場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象、讀取或設(shè)置一個(gè)類(lèi)的靜態(tài)字段(被final修飾、已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候,以及調(diào)用類(lèi)的靜態(tài)方法的時(shí)候。
2.使用java.lang.reflect包的方法對(duì)類(lèi)進(jìn)行反射調(diào)用的時(shí)候
3.當(dāng)初始化一個(gè)類(lèi)的時(shí)候,如果發(fā)現(xiàn)其父類(lèi)還沒(méi)有進(jìn)行過(guò)初始化、則需要先出發(fā)其父類(lèi)的初始化
4.jvm啟動(dòng)時(shí),用戶(hù)指定一個(gè)執(zhí)行的主類(lèi)(包含main方法的那個(gè)類(lèi)),虛擬機(jī)會(huì)先初始化這個(gè)類(lèi)
在上面準(zhǔn)備階段 public static int value = 12; 在準(zhǔn)備階段完成后 value的值為0,而在初始化階調(diào)用了類(lèi)構(gòu)造器<clinit >()方法,這個(gè)階段完成后value的值為12。

類(lèi)加載器

虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把類(lèi)加載階段中的“通過(guò)一個(gè)類(lèi)的全限定名來(lái)獲取描述此類(lèi)的二進(jìn)制字節(jié)流”這個(gè)動(dòng)作放到Java虛擬機(jī)外部去實(shí)現(xiàn),以便讓?xiě)?yīng)用程序自己決定如何去獲取所需要的類(lèi)。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱(chēng)為“類(lèi)加載器”。

類(lèi)與類(lèi)加載器

對(duì)于任何一個(gè)類(lèi),都需要由加載它的類(lèi)加載器和這個(gè)類(lèi)來(lái)確立其在JVM中的唯一性。也就是說(shuō),兩個(gè)類(lèi)來(lái)源于同一個(gè)Class文件,并且被同一個(gè)類(lèi)加載器加載,這兩個(gè)類(lèi)才相等。比如同一個(gè)類(lèi)采用不同的類(lèi)加載器去加載,在判斷對(duì)象所屬類(lèi)型檢查(instanceof)時(shí)會(huì)出現(xiàn)不同。

雙親委派模型

從虛擬機(jī)的角度來(lái)說(shuō),只存在兩種不同的類(lèi)加載器:一種是啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader),該類(lèi)加載器使用C++語(yǔ)言實(shí)現(xiàn),屬于虛擬機(jī)自身的一部分。另外一種就是所有其它的類(lèi)加載器,這些類(lèi)加載器是由Java語(yǔ)言實(shí)現(xiàn),獨(dú)立于JVM外部,并且全部繼承自抽象類(lèi)java.lang.ClassLoader。

從Java開(kāi)發(fā)人員的角度來(lái)看,大部分Java程序一般會(huì)使用到以下三種系統(tǒng)提供的類(lèi)加載器:
1)啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader):負(fù)責(zé)加載存放在%JAVA_HOME%\lib目錄中的,或者被-Xbootclasspath參數(shù)所指定的路徑中的,并且被java虛擬機(jī)識(shí)別的(僅按照文件名識(shí)別,如rt.jar,名字不符合的類(lèi)庫(kù),即使放在指定路徑中也不會(huì)被加載)類(lèi)庫(kù)到虛擬機(jī)的內(nèi)存中,啟動(dòng)類(lèi)加載器無(wú)法被java程序直接引用。 
2)擴(kuò)展類(lèi)加載器(Extension ClassLoader):由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),負(fù)責(zé)加載%JAVA_HOME%\lib\ext目錄中的,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類(lèi)庫(kù),開(kāi)發(fā)者可以直接使用擴(kuò)展類(lèi)加載器。
3)應(yīng)用程序類(lèi)加載器(Application ClassLoader):由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn),負(fù)責(zé)加載用戶(hù)類(lèi)路徑classpath上所指定的類(lèi)庫(kù),是類(lèi)加載器ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱(chēng)為系統(tǒng)類(lèi)加載器,開(kāi)發(fā)者可以直接使用應(yīng)用程序類(lèi)加載器,如果程序中沒(méi)有自定義過(guò)類(lèi)加載器,該加載器就是程序中默認(rèn)的類(lèi)加載器
我們的應(yīng)用程序都是由這三類(lèi)加載器互相配合進(jìn)行加載的。 
另外還有自定義類(lèi)加載器。 
4)自定義類(lèi)加載器(必須繼承 ClassLoader)。

                                    怎么理解java虛擬機(jī)執(zhí)行子系統(tǒng)

                                                 圖-類(lèi)加載器雙親委派模型

如果一個(gè)類(lèi)加載器收到了類(lèi)加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類(lèi),而是把這個(gè)請(qǐng)求委派給父類(lèi)加載器去完成,每一個(gè)層次的父加載器都是如此,因此所有的請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類(lèi)加載器中,只有當(dāng)父類(lèi)加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去加載。雙親委派模型對(duì)于保證JAVA程序的穩(wěn)定運(yùn)作很重要。例如可以嘗試去編寫(xiě)一個(gè)與rt.jar類(lèi)庫(kù)中已有類(lèi)重名的Java類(lèi),將會(huì)發(fā)現(xiàn)可以正常編譯,但永遠(yuǎn)無(wú)法被加載運(yùn)行。

如何破壞雙親委派模型

虛擬機(jī)字節(jié)碼執(zhí)行引擎

執(zhí)行引擎是Java虛擬機(jī)最核心的組成部分之一。虛擬機(jī)是一個(gè)相對(duì)于物理機(jī)的概念,這兩種機(jī)器都有代碼執(zhí)行能力,其區(qū)別是物理機(jī)的執(zhí)行引擎是直接建立在處理器、硬件、指令集和操作系統(tǒng)層面的,而虛擬機(jī)的執(zhí)行引擎則是由自己實(shí)現(xiàn)的,因此可以自行制定指令集與執(zhí)行引擎的結(jié)構(gòu)體系,并且能夠執(zhí)行那些不被硬件直接支持的指令集格式。

在Java虛擬機(jī)規(guī)范中制定了虛擬機(jī)字節(jié)碼執(zhí)行引擎的概念模型,這個(gè)概念模型成為各種虛擬機(jī)執(zhí)行引擎的統(tǒng)一外觀(guān)(Facade)。在不同的虛擬機(jī)實(shí)現(xiàn)里面,執(zhí)行引擎在執(zhí)行Java代碼的時(shí)候會(huì)有解釋執(zhí)行(通過(guò)解釋器執(zhí)行)和編譯執(zhí)行(通過(guò)即時(shí)編譯器產(chǎn)生本地代碼執(zhí)行)兩種選擇,也可能兩者兼?zhèn)?,甚至可能?huì)包含幾個(gè)不同級(jí)別的編譯器執(zhí)行引擎。 

方法調(diào)用

解析

方法調(diào)用并不等同于方法執(zhí)行,方法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用方法的版本(即調(diào)用哪一個(gè)方法),暫時(shí)還不涉及方法內(nèi)部的具體運(yùn)行過(guò)程。我們知道,Class文件的編譯過(guò)程中并不包括傳統(tǒng)編譯中的連接步驟,一切方法調(diào)用在Class文件調(diào)用里面存儲(chǔ)的都只是符號(hào)引用,而不是方法在實(shí)際運(yùn)行時(shí)的內(nèi)存布局入口地址(相當(dāng)于之前說(shuō)的直接引用),也就是說(shuō)符號(hào)引用解析成直接引用的過(guò)程。這個(gè)特性使得Java 具有強(qiáng)大的動(dòng)態(tài)擴(kuò)展能力,但也使得Java方法調(diào)用過(guò)程變得復(fù)雜起來(lái),需要在類(lèi)加載器件,甚至是運(yùn)行期間才確定目標(biāo)方法的直接引用。

在類(lèi)加載的解析階段,會(huì)將其中一部分符號(hào)引用直接轉(zhuǎn)化為直接引用,前提是:方法在程序真正運(yùn)行之前就有一個(gè)可確定的版本,并且這個(gè)方法的調(diào)用版本在運(yùn)行期是不可改變的。換句話(huà)說(shuō),調(diào)用目標(biāo)在程序代碼寫(xiě)好,編譯器進(jìn)行編譯時(shí)就必須確定下來(lái)。這類(lèi)方法的調(diào)用稱(chēng)為解析(Resolution)。

在Java語(yǔ)言中符合“編譯期可知,運(yùn)行期不可變”這個(gè)要求的方法,主要包括:靜態(tài)方法和私有方法。前者與類(lèi)型直接關(guān)聯(lián),后者在外部不可被訪(fǎng)問(wèn),這兩種方法各自的特點(diǎn)決定了他們都不可能通過(guò)繼承或別的方式重寫(xiě)其它版本,因此它們適合在類(lèi)加載階段進(jìn)行解析。

與之相對(duì)應(yīng)的,Java 虛擬機(jī)里面提供了5條方法調(diào)用字節(jié)碼指令,分別如下:
invokestatic:調(diào)用靜態(tài)方法
invokespecial:調(diào)用<init>方法、私有方法和父類(lèi)方法
invokevirtual:調(diào)用所有的虛方法
invokeinterface:調(diào)用接口方法,會(huì)在運(yùn)行時(shí)在確定一個(gè)實(shí)現(xiàn)此接口的對(duì)象
invokedynamic:會(huì)在運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法,然后再執(zhí)行該方法。

只要能被invokestatic和invokespecial調(diào)用的方法,都可以在解析階段中確定唯一的調(diào)用版本,符合這個(gè)條件的有靜態(tài)方法、私有方法、實(shí)例構(gòu)造器、父類(lèi)方法4類(lèi),它們?cè)诩虞d的時(shí)候就會(huì)把符號(hào)引用解析為該方法的直接引用,這些方法稱(chēng)為非虛方法,由于final修飾的方法不能被覆蓋,也屬于非虛方法。與之相反,其他的方法稱(chēng)為虛方法。

解析調(diào)用一定是靜態(tài)的過(guò)程,在編譯期間完全確定,在類(lèi)裝載的解析階段就會(huì)把涉及的符號(hào)引用全部轉(zhuǎn)換為可確定的直接引用,不會(huì)延遲到運(yùn)行期再去完成。這和后邊談到的分派是完全不同的。

分派

作為一門(mén)面向?qū)ο蟮某绦蛘Z(yǔ)言,Java具備面型對(duì)象的3個(gè)特征:繼承、封裝和多態(tài)。下面我們將會(huì)講解多態(tài)性特征的一些最基本的體現(xiàn),如“重寫(xiě)”和“重載”在Java虛擬機(jī)中是怎么實(shí)現(xiàn)的。

靜態(tài)分派

依賴(lài)于靜態(tài)類(lèi)型來(lái)定位方法執(zhí)行版本的分派動(dòng)作(如重載)稱(chēng)為靜態(tài)分派。虛擬機(jī)(準(zhǔn)確說(shuō)是編譯器)在重載時(shí)是通過(guò)參數(shù)的靜態(tài)類(lèi)型而不是實(shí)際類(lèi)型作為判定依據(jù)的,并且靜態(tài)類(lèi)型是編譯器可知的,因此在編譯期,Javac編譯器會(huì)根據(jù)參數(shù)的靜態(tài)類(lèi)型決定使用哪個(gè)重載版本。

動(dòng)態(tài)分派

運(yùn)行時(shí)期依賴(lài)于實(shí)際類(lèi)型來(lái)定位方法執(zhí)行的分派動(dòng)作(重寫(xiě)Override)屬于動(dòng)態(tài)分派。

單分派與多分派

方法的接受者與方法的參數(shù)統(tǒng)稱(chēng)為方法的宗量。根據(jù)分派基于多少宗量,可以將分派劃分為單分派和多分派兩種。單分派是根據(jù)一個(gè)宗量對(duì)目標(biāo)方法進(jìn)行選擇,多分派則是根據(jù)多于一個(gè)宗量對(duì)目標(biāo)方法進(jìn)行選擇。
在靜態(tài)分派的過(guò)程中,選擇目標(biāo)方法的依據(jù)有兩點(diǎn),對(duì)象的靜態(tài)類(lèi)型以及方法參數(shù)的類(lèi)型和數(shù)量。因?yàn)槭歉鶕?jù)兩個(gè)宗量進(jìn)行選擇,所以Java語(yǔ)言的靜態(tài)分派屬于多分派類(lèi)型。
在動(dòng)態(tài)分派的過(guò)程中,由于編譯器已經(jīng)決定了目標(biāo)方法的簽名,因此只需要找到方法的接受者就可以了。因?yàn)槭歉鶕?jù)一個(gè)宗量進(jìn)行選擇,所以Java語(yǔ)言的動(dòng)態(tài)分派屬單分派類(lèi)型。

虛擬機(jī)動(dòng)態(tài)分派的實(shí)現(xiàn)

由于動(dòng)態(tài)分配是非常頻繁的動(dòng)作,而且動(dòng)態(tài)分配的方法版本選擇過(guò)程需要運(yùn)行時(shí)在類(lèi)的方法元數(shù)據(jù)中搜索合適的目標(biāo)方法,因此在虛擬機(jī)的實(shí)際實(shí)現(xiàn)中,基于性能的考慮,大部分實(shí)現(xiàn)都不會(huì)真正的進(jìn)行如此頻繁的搜索。最常用的手段就是為類(lèi)在方法區(qū)中建立一個(gè)虛方法表(Virtual Method Table , 也稱(chēng)為vtable ,與此對(duì)應(yīng)的在invokeinterface執(zhí)行時(shí)也會(huì)用到接口方法表-Inteface Method Table,簡(jiǎn)稱(chēng)itable),使用虛方法表索引來(lái)代替元數(shù)據(jù)查找以提高性能。

虛方法表中存放著各個(gè)方法的實(shí)際入口地址。如果某個(gè)方法在子類(lèi)中沒(méi)有被重寫(xiě),那子類(lèi)的虛方法表里面的地址入口和父類(lèi)相同方法的地址入口是一致的,都指向父類(lèi)的實(shí)現(xiàn)入口。如果子類(lèi)中重寫(xiě)了這個(gè)方法,子類(lèi)方法表中的地址將會(huì)替換為指向子類(lèi)實(shí)現(xiàn)版本的入口。方法表一般在類(lèi)加載的連接階段進(jìn)行初始化,準(zhǔn)備了類(lèi)的變量初始值之后,虛擬機(jī)會(huì)把該類(lèi)的方法表也初始化完畢。

到此,相信大家對(duì)“怎么理解java虛擬機(jī)執(zhí)行子系統(tǒng)”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!

本文標(biāo)題:怎么理解java虛擬機(jī)執(zhí)行子系統(tǒng)
網(wǎng)頁(yè)URL:http://muchs.cn/article42/gpjsec.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供軟件開(kāi)發(fā)、網(wǎng)站設(shè)計(jì)公司、外貿(mào)網(wǎng)站建設(shè)、云服務(wù)器、微信公眾號(hào)、App開(kāi)發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)公司