wordpress中文團(tuán)隊(duì)哈爾濱seo推廣
第八章 虛擬機(jī)字節(jié)碼執(zhí)行引擎
8.1 意義
不受物理?xiàng)l件制約地定制指令集與執(zhí)行引擎的結(jié)構(gòu)體系,能夠執(zhí)行那些不被硬件直接支持的指令集格式。輸入的是字節(jié)碼二進(jìn)制流,處理過(guò)程是字節(jié)碼解析執(zhí)行的等效過(guò)程,輸出的是執(zhí)行結(jié)果
8.2 運(yùn)行時(shí)棧幀結(jié)構(gòu)
-
棧幀存儲(chǔ)了方法的局部變量表、操作數(shù)棧、動(dòng)態(tài)連接和方法返回地址等信息
以Java程序的角度來(lái)看,同一時(shí)刻、同一條線程里面,在調(diào)用堆棧的所有方法都同時(shí)處于執(zhí)行狀態(tài)。而對(duì)于執(zhí)行引擎來(lái)講在活動(dòng)線程中,只有位于棧頂?shù)姆椒ú攀窃谶\(yùn)行的,只有位于棧頂?shù)臈攀巧У?#xff0c;其被稱(chēng)為“當(dāng)前棧幀”(Current Stack Frame),與這個(gè)棧幀所關(guān)聯(lián)的方法被稱(chēng)為“當(dāng)前方法”(Current Method)
- 編譯Java程序源碼的時(shí)候,棧幀中需要多大的局部變量表,需要多深的操作數(shù)棧就已經(jīng)被分析計(jì)算出來(lái),也就是一個(gè)棧幀需要分配多少內(nèi)存,并不會(huì)受到程序運(yùn)行期變量數(shù)據(jù)的影響,而僅僅取決于程序源碼和具體的虛擬機(jī)實(shí)現(xiàn)的棧內(nèi)存布局形式
8.2.1 局部變量表
- 存放方法參數(shù)和方法內(nèi)部定義的局部變量,編譯時(shí)就確定了該方法局部變量表的最大容量。
- 變量槽(Slot)為最小單位,一個(gè)變量槽可以存放一個(gè)32位以?xún)?nèi)的數(shù)據(jù)類(lèi)型,對(duì)于64位的數(shù)據(jù)類(lèi)型,Java虛擬機(jī)會(huì)以高位對(duì)齊的方式為其分配兩個(gè)連續(xù)的變量槽空間。Java語(yǔ)言中明確的64位的數(shù)據(jù)類(lèi)型只有l(wèi)ong和double兩種
- Java虛擬機(jī)通過(guò)索引定位的方式使用局部變量表,索引值的范圍是從0開(kāi)始至局部變量表最大的變量槽數(shù)量。
- 當(dāng)一個(gè)方法被調(diào)用時(shí),Java虛擬機(jī)會(huì)使用局部變量表來(lái)完成參數(shù)值到參數(shù)變量列表的傳遞過(guò)程,即實(shí)參到形參的傳遞。如果執(zhí)行的是實(shí)例方法, 表中第0位索引,默認(rèn)是記錄方法所屬對(duì)象實(shí)例的引用。
- 變量槽可以重用,但是可能會(huì)影響垃圾收集
- 局部變量不初始化不能使用
8.2.2 操作數(shù)棧
-
當(dāng)一個(gè)方法剛剛開(kāi)始執(zhí)行的時(shí)候,這個(gè)方法的操作數(shù)棧是空的,在方法的執(zhí)行過(guò)程中,會(huì)有各種字節(jié)碼指令往操作數(shù)棧中寫(xiě)入和提取內(nèi)容,也就是出棧和入棧操作。顧名思義,就是用來(lái)操作的。
-
操作數(shù)棧中元素的數(shù)據(jù)類(lèi)型必須與字節(jié)碼指令的序列嚴(yán)格匹配,也就是說(shuō)用于整型值的操作,不能使用其他的值。
-
棧幀可以一部分重疊,可以共用一部分?jǐn)?shù)據(jù)
8.2.3 動(dòng)態(tài)連接
每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(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)連接
8.2.4 方法返回地址
當(dāng)一個(gè)方法開(kāi)始執(zhí)行后,只有兩種方式退出這個(gè)方法:
- 第一種方式是執(zhí)行引擎遇到任意一個(gè)方法返回的字節(jié)碼指令,這時(shí)候可能會(huì)有返回值傳遞給上層的方法調(diào)用者
- 另外一種退出方式是在方法執(zhí)行的過(guò)程中遇到了異常
一般來(lái)說(shuō),方法正常退出時(shí),主調(diào)方法的PC計(jì)數(shù)器的值就可以作為返回地址,而方法異常退出時(shí),返回地址是要通過(guò)異常處理器表來(lái)確定的。
8.3 方法調(diào)用
方法調(diào)用階段唯一的任務(wù)就是確定調(diào)用哪個(gè)方法,不涉及方法內(nèi)部細(xì)節(jié)
8.3.1 解析
解析是指當(dāng)一個(gè)方法被調(diào)用時(shí),JVM 如何確定實(shí)際要執(zhí)行的方法。這里的解析調(diào)用具體為:方法的調(diào)用版本在編譯期間就完全確定,在類(lèi)加載的解析階段就會(huì)把涉及的符號(hào)引用全部轉(zhuǎn)變?yōu)槊鞔_的直接引用,在運(yùn)行期是不可改變的方法。
主要有靜態(tài)方法和私有方法兩類(lèi),適合在類(lèi)加載階段進(jìn)行解析
有五種調(diào)用方法的字節(jié)碼指令:
invokestatic
用于調(diào)用靜態(tài)方法invokespecial
用于調(diào)用實(shí)例構(gòu)造器()方法、私有方法和父類(lèi)中的方法invokevirtual
用于調(diào)用所有的虛方法invokeinterface
用于調(diào)用接口方法,會(huì)在運(yùn)行時(shí)再確定一個(gè)實(shí)現(xiàn)該接口的對(duì)象invokedynamic
先在運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法,然后再執(zhí)行該方法,由用戶(hù)制定邏輯,前4種固定在虛擬機(jī)內(nèi)部
只要能被invokestatic和invokespecial指令調(diào)用的方法,都可以在解析階段中確定唯一的調(diào)用版本,Java語(yǔ)言里符合這個(gè)條件的方法共有靜態(tài)方法、私有方法、實(shí)例構(gòu)造器、父類(lèi)方法4種,再加上被final修飾的方法(盡管它使用invokevirtual指令調(diào)用),這5種方法調(diào)用會(huì)在類(lèi)加載的時(shí)候就可以把符號(hào)引用解析為該方法的直接引用。這些方法統(tǒng)稱(chēng)為“非虛方法”(Non-Virtual Method),與之相反,其他方法就被稱(chēng)為“虛方法”(Virtual Method)。
8.3.2 分派
分派調(diào)用是一種方法調(diào)用形式,可以是靜態(tài)的也可是動(dòng)態(tài)的。分派具有動(dòng)態(tài)性,可以揭示多態(tài)的特性,比如重寫(xiě)和重載,根據(jù)實(shí)際代碼更好理解
- 靜態(tài)分派
- 所有依賴(lài)靜態(tài)類(lèi)型來(lái)決定方法執(zhí)行版本的分派動(dòng)作,都稱(chēng)為靜態(tài)分派。靜態(tài)分派的最典型應(yīng)用表
現(xiàn)就是方法重載。靜態(tài)分派發(fā)生在編譯階段,因此確定靜態(tài)分派的動(dòng)作實(shí)際上不是由虛擬機(jī)來(lái)執(zhí)行
的。 - 虛擬機(jī)(或者準(zhǔn)確地說(shuō)是編譯器)在重載時(shí)是通過(guò)參數(shù)的靜態(tài)類(lèi)型而不是實(shí)際類(lèi)型作為
判定依據(jù)的。由于靜態(tài)類(lèi)型在編譯期可知,所以在編譯階段,Javac編譯器就根據(jù)參數(shù)的靜態(tài)類(lèi)型決定
了會(huì)使用哪個(gè)重載版本 - 需要注意Javac編譯器雖然能確定出方法的重載版本,但在很多情況下這個(gè)重載版本并不是“唯
一”的,往往只能確定一個(gè)“相對(duì)更合適的”版本
- 所有依賴(lài)靜態(tài)類(lèi)型來(lái)決定方法執(zhí)行版本的分派動(dòng)作,都稱(chēng)為靜態(tài)分派。靜態(tài)分派的最典型應(yīng)用表
- 動(dòng)態(tài)分派
- 在運(yùn)行期根據(jù)實(shí)際類(lèi)型確定方法執(zhí)行版本的分派過(guò)程稱(chēng)為動(dòng)態(tài)分派。也就是方法重寫(xiě)的本質(zhì)
- 根源在于虛方法調(diào)用指令invokevirtual的執(zhí)行邏輯:
- 找到操作數(shù)棧頂?shù)牡谝粋€(gè)元素所指向的對(duì)象的實(shí)際類(lèi)型,記作C
- 如果在類(lèi)型C中找到與常量中的描述符和簡(jiǎn)單名稱(chēng)都相符的方法,則進(jìn)行訪問(wèn)權(quán)限校驗(yàn),如果通過(guò)則返回這個(gè)方法的直接引用,查找過(guò)程結(jié)束;不通過(guò)則返回java.lang.IllegalAccessError異常。
- 否則,按照繼承關(guān)系從下往上依次對(duì)C的各個(gè)父類(lèi)進(jìn)行第二步的搜索和驗(yàn)證過(guò)程
- 如果始終沒(méi)有找到合適的方法,則拋出java.lang.AbstractMethodError異常
- 在Java里面只有虛方法存在,字段永遠(yuǎn)不可能是虛的,換句話說(shuō),字段永遠(yuǎn)不參與多態(tài)。當(dāng)子類(lèi)聲明了與父類(lèi)同名的字段時(shí),雖然在子類(lèi)的內(nèi)存中兩個(gè)字段都會(huì)存在,但是子類(lèi)的字段會(huì)遮蔽父類(lèi)的同名字段
- 單分派與多分派
- 方法的接收者與方法的參數(shù)統(tǒng)稱(chēng)為方法的宗量,單分派是根據(jù)一個(gè)宗量對(duì)目標(biāo)方法進(jìn)行選擇,多分派則是根據(jù)多于一個(gè)宗量對(duì)目標(biāo)方法進(jìn)行選擇。
- 虛擬機(jī)動(dòng)態(tài)分派的實(shí)現(xiàn)
- 動(dòng)態(tài)分派的方法版本選擇過(guò)程需要運(yùn)行時(shí)在接收者類(lèi)型的方法元數(shù)據(jù)中搜索合適的目標(biāo)方法
- 一種優(yōu)化方法是為類(lèi)型在方法區(qū)中建立一個(gè)虛方法表,使用虛方法表索引來(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)的虛方法表也一同初始化完畢。
- 除此之外,還會(huì)使用類(lèi)型繼承關(guān)系分析(Class Hierarchy Analysis,CHA)、守護(hù)內(nèi)聯(lián)(Guarded Inlining)、內(nèi)聯(lián)緩存(InlineCache)等多種非穩(wěn)定的激進(jìn)優(yōu)化來(lái)爭(zhēng)取更大的性能空間