java虚拟机调用linux_Java虚拟机字节码执行引擎
定義
Java虛擬機(jī)字節(jié)碼執(zhí)行引擎是jvm最核心的組成部分之一,它做的事情很簡單:輸入的是字節(jié)碼文件,處理過程是字節(jié)碼解析的等效過程,輸出的是執(zhí)行結(jié)果。在不同的虛擬機(jī)實(shí)現(xiàn)里,執(zhí)行引擎在執(zhí)行java代碼的時候可能會有解釋執(zhí)行和編譯執(zhí)行兩種選擇,也可能兩者兼?zhèn)洹?/p>
運(yùn)行時棧幀結(jié)構(gòu)
java字節(jié)碼執(zhí)行引擎在調(diào)用和執(zhí)行方法的時候使用了一種叫做棧幀的數(shù)據(jù)結(jié)構(gòu)。
在jvm的內(nèi)存結(jié)構(gòu)里,存在這一塊稱為虛擬機(jī)棧的內(nèi)存區(qū)域,虛擬機(jī)棧中的元素就是棧幀。每個方法的調(diào)用至結(jié)束對應(yīng)著一個棧幀在虛擬機(jī)棧的入棧和出棧。
棧幀數(shù)據(jù)結(jié)構(gòu)示意圖:
如圖所示,因?yàn)樘摂M機(jī)棧是線程私有的,所以每個線程都有自己的虛擬機(jī)棧;而每個線程的虛擬機(jī)棧中都有多個棧幀對應(yīng)一個方法調(diào)用鏈中的多個方法,棧頂?shù)臈钱?dāng)前正在執(zhí)行的方法;每個棧幀主要包含4個部分:
局部變量表
操作數(shù)棧
動態(tài)連接
方法返回地址
在編譯代碼的階段,棧幀中需要多大的局部變量表,多深的操作數(shù)棧都是已經(jīng)完全確定的,而且會寫入到方法表的Code屬性中。
接下來一次介紹棧幀中的4個部分:
局部變量表
局部變量表是一組變量值存儲空間,用于存放方法參數(shù)和方法內(nèi)局部變量。
局部變量表的容量以變量槽Slot為最小單位,規(guī)定一個Slot可以存放一個32位以內(nèi)的數(shù)據(jù)類型,java中占32位以內(nèi)的數(shù)據(jù)類型有8種基本數(shù)據(jù)除了long和double以外的6種,還有reference和returnAddress,共8種類型。而對于64位的數(shù)據(jù)類型,即long和double,則會以高位對齊的方式為其分配兩個連續(xù)的Slot空間。
在方法執(zhí)行時,方法的參數(shù)列表是通過局部變量表傳遞的,如果執(zhí)行的是實(shí)例方法(非static方法),則局部變量表中的第0位索引的Slot默認(rèn)保存方法所屬對象的引用,以支持“this”關(guān)鍵字來訪問對象數(shù)據(jù)。其余參數(shù)則按參數(shù)列表順序,占用從1開始的局部變量Slot,參數(shù)表分配完成后,在根據(jù)方法體內(nèi)局部變量定義的順序和作用域?yàn)榫植孔兞糠峙淦溆郤lot。之所以這里要強(qiáng)調(diào)作用域是因?yàn)闉榱斯?jié)省棧幀空間,局部變量表中的Slot是可以重用的,當(dāng)一個Slot中保存的局部變量超出其作用域后,這個Slot可以被復(fù)用來存儲新的變量。
操作數(shù)棧
操作數(shù)棧是用來執(zhí)行字節(jié)碼命令的,比如iadd命令在運(yùn)行的時候,就是把操作數(shù)棧中最接近棧頂?shù)?個元素出棧相加,然后將結(jié)果入棧。
操作數(shù)棧的每一個元素可以是任意的java數(shù)據(jù)類型,32位數(shù)據(jù)所占棧容量為1,64位數(shù)據(jù)所占棧容量為2。
java虛擬機(jī)的解釋執(zhí)行引擎是“基于棧的執(zhí)行引擎”,這里的棧就是指操作數(shù)棧。
動態(tài)連接
一個指向運(yùn)行時常量池的引用,用來支持當(dāng)前方法的代碼實(shí)現(xiàn)動態(tài)鏈接。
方法返回地址
一個方法開始后,只有兩種方式可以退出,一種是當(dāng)執(zhí)行引擎遇到任意一個返回字節(jié)碼指令的時候,另一種是在執(zhí)行中遇到異常并且沒有捕獲的時候。無論以哪種方式退出,退出后都需要返回到方法被調(diào)用的位置,因此需要在棧幀中保存一些信息,用來恢復(fù)它上層方法的執(zhí)行狀態(tài)。
方法調(diào)用
方法調(diào)用不等于方法執(zhí)行,方法調(diào)用階段的唯一目的就是確定被調(diào)用方法的版本。
解析
我們知道,在虛擬機(jī)進(jìn)行類加載的時候,有一個階段叫做“解析”,在解析階段會將常量池中的一部分符號引用轉(zhuǎn)化為直接引用,在這個階段,有一部分方法調(diào)用就已經(jīng)被解析為了直接引用,解析的前提是,這部分方法的調(diào)用目標(biāo)在編譯階段就可以確定下來。
在java虛擬機(jī)中共有5個方法調(diào)用指令:
invokestatic
invokespecial
invokevirtual
invokeinterface
invokedynamic
可以在“解析”階段就確認(rèn)調(diào)用目標(biāo)的有invokestatic和invokespecial指令調(diào)用的方法以及invokevirtual調(diào)用的final方法,這些方法被稱為“非虛方法”,與之相反的被稱為“虛方法”。
分派
分派分為“靜態(tài)分派”和“動態(tài)分派”。
靜態(tài)分派
靜態(tài)分派用來實(shí)現(xiàn)java語言的"重載"特性。
當(dāng)我們聲明一個變量時,可能會用到這種形式:Human man=new Man();(Man是Human子類),對于man這個變量來說,Human叫做它的靜態(tài)類型,Man叫做它的實(shí)際類型。
"重載"是基于靜態(tài)類型。也就是說,對于相同名稱,參數(shù)列表不同的方法,選擇哪個方法取決于參數(shù)列表中參數(shù)的靜態(tài)類型,而靜態(tài)類型在編譯時是可知的,因此靜態(tài)分派發(fā)生在編譯階段。
動態(tài)分派
動態(tài)分派用來實(shí)現(xiàn)java語言的“重寫”特性,動態(tài)分派對應(yīng)invokevirtual命令。
invokevirtual命令的執(zhí)行過程如下:
找到操作數(shù)棧棧頂?shù)牡谝粋€元素所指向的對象(也被稱為方法的接收者)實(shí)際類型,記為C。
如果在C中找到與常量中的描述符和簡單名稱都相符的方法,則進(jìn)行訪問權(quán)限驗(yàn)證,如果通過則返回這個方法的直接引用,查找結(jié)束;如果不通過,則返回java.lang.IllegalAccessError異常。
否則,按照繼承關(guān)系從下往上依次對C的各個父類進(jìn)行第2步的搜索和驗(yàn)證過程。
如果最終沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常。
我們可以看出invokevirtual指令執(zhí)行的時候是由對象的實(shí)際類型決定調(diào)用哪個方法版本的,而對象的實(shí)際類型只有到運(yùn)行期的時候才能確定,我們把這種在運(yùn)行期確定方法版本的分派過程稱為“動態(tài)分派”。
單分派和多分派
方法的接收者與方法的參數(shù)統(tǒng)稱為方法的宗量,根據(jù)分派基于宗量的多少,將分派分為單分派和多分派兩種。
目前的java語言中,靜態(tài)分派的時候,是基于接收者和方法參數(shù)兩個宗量進(jìn)行的,因此靜態(tài)分派是多分派;動態(tài)分派的時候,是基于接收者一個宗量進(jìn)行的,因此動態(tài)分派是單分派。
方法執(zhí)行
一般從程序代碼到物理機(jī)可以運(yùn)行的目標(biāo)代碼都要經(jīng)歷下圖所示的過程:
圖中展示了編譯執(zhí)行和解釋執(zhí)行這兩種路徑,無論是哪種執(zhí)行方式,一般都會先通過詞法分析和語法分析把源代碼轉(zhuǎn)化為抽象語法樹(AST)。
然后從抽象語法樹節(jié)點(diǎn)開始,中間的分支代表的是解釋執(zhí)行過程,下邊的分支代表的是編譯執(zhí)行過程。
jvm執(zhí)行字節(jié)碼是采用的解釋執(zhí)行,javac編譯器完成了程序代碼經(jīng)過詞法分析、語法分析到抽象語法樹,再遍歷語法樹生成線性的字節(jié)碼指令流的過程。而解釋器則在虛擬機(jī)的內(nèi)部。
javac編譯器輸出的字節(jié)碼指令流是一種基于棧的指令集架構(gòu),指令流中的大部分指令都是零地址指令,它們依賴操作數(shù)棧進(jìn)行工作。與之相對的是基于寄存器的指令集。
基于棧的指令集有以下優(yōu)點(diǎn):
可移植
代碼更加緊湊(不需要存放參數(shù))
編譯器實(shí)現(xiàn)更簡單(不需要考慮空間分配問題,所需空間都在棧上操作)
基于棧的指令集的主要缺點(diǎn)就是執(zhí)行速度慢:一個是因?yàn)榛跅M瓿上嗤δ芩枰闹噶罴瘮?shù)量一般比基于寄存器要多,因?yàn)槿霔?、出棧會產(chǎn)生多余的指令數(shù)量;另一個是因?yàn)闂J腔趦?nèi)存實(shí)現(xiàn)的,內(nèi)存的讀取速度本身就比處理器寄存器要慢,這一點(diǎn)可以采取棧頂緩存的手段,把最常用的操作映射到寄存器中避免直接訪問內(nèi)存,但是畢竟只是優(yōu)化措施,不能解決本質(zhì)問題。
執(zhí)行方法中的代碼本質(zhì)就是通過棧的指令集來進(jìn)行運(yùn)算,這里就不詳述了。
總結(jié)
以上是生活随笔為你收集整理的java虚拟机调用linux_Java虚拟机字节码执行引擎的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: acm java输入输出_在竞赛ACM
- 下一篇: linux看电视系统,教你如何在Linu