虛擬機(jī)字節(jié)碼執(zhí)行引擎
執(zhí)行引擎在執(zhí)行Java代碼的時(shí)候可能會(huì)有解釋執(zhí)行(通過解釋器執(zhí)行)和編譯執(zhí)行(通過即時(shí)編譯器產(chǎn)生本地代碼執(zhí)行)兩種選擇,也可能兩者兼?zhèn)洌踔吝€可能會(huì)包含幾個(gè)不同級別的編譯器執(zhí)行引擎。
運(yùn)行時(shí)棧幀結(jié)構(gòu)
棧幀存儲(chǔ)了方法的局部變量表、 操作數(shù)棧、 動(dòng)態(tài)連接和方法返回地址等信息。 (詳情見《深入理解Java虛擬機(jī)》(學(xué)習(xí)筆記(一)))
在活動(dòng)線程中,只有位于棧頂?shù)臈攀怯行У模Q為當(dāng)前棧幀(Current StackFrame),與這個(gè)棧幀相關(guān)聯(lián)的方法稱為當(dāng)前方法(Current Method)。
局部變量表
是一組變量值存儲(chǔ)空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。
局部變量表的容量以變量槽(Variable Slot,下稱Slot)為最小單位
如果執(zhí)行的是實(shí)例方法(非static的方法),那局部變量表中第0位索引的Slot默認(rèn)是用于傳遞方法所屬對象實(shí)例的引用,在方法中可以通過關(guān)鍵字“this”來訪問到這個(gè)隱含的參數(shù)。 其余參數(shù)則按照參數(shù)表順序排列,占用從1開始的局部變量Slot,參數(shù)表分配完畢后,再根據(jù)方法體內(nèi)部定義的變量順序和作用域分配其余的Slot。
局部變量不存在“準(zhǔn)備階段”(詳情見《深入理解Java虛擬機(jī)》(學(xué)習(xí)筆記(五)))。如果一個(gè)局部變量定義了但沒有賦初始值是不能使用的。
操作數(shù)棧
是一個(gè)后入先出(Last In FirstOut,LIFO)棧
動(dòng)態(tài)連接
每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,持有這個(gè)引用是為了支持方法調(diào)用過程中的動(dòng)態(tài)連接(Dynamic Linking)。
方法返回地址
當(dāng)一個(gè)方法開始執(zhí)行后,只有兩種方式可以退出這個(gè)方法。
- 第一種方式是執(zhí)行引擎遇到任意一個(gè)方法返回的字節(jié)碼指令,這時(shí)候可能會(huì)有返回值傳遞給上層的方法調(diào)用者(調(diào)用當(dāng)前方法的方法稱為調(diào)用者),是否有返回值和返回值的類型將根據(jù)遇到何種方法返回指令來決定,這種退出方法的方式稱為正常完成出口(Normal Method Invocation Completion)。
- 另外一種退出方式是,在方法執(zhí)行過程中遇到了異常,并且這個(gè)異常沒有在方法體內(nèi)得到處理,無論是Java虛擬機(jī)內(nèi)部產(chǎn)生的異常,還是代碼中使用athrow字節(jié)碼指令產(chǎn)生的異常,只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會(huì)導(dǎo)致方法退出,這種退出方法的方式稱為異常完成出口(Abrupt Method Invocation Completion)。 一個(gè)方法使用異常完成出口的方式退出,是不會(huì)給它的上層調(diào)用者產(chǎn)生任何返回值的。
附加信息
虛擬機(jī)規(guī)范允許具體的虛擬機(jī)實(shí)現(xiàn)增加一些規(guī)范里沒有描述的信息到棧幀之中
方法調(diào)用
方法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用方法的版本
一切方法調(diào)用在Class文件里面存儲(chǔ)的都只是符號引用,而不是方法在實(shí)際運(yùn)行時(shí)內(nèi)存布局中的入口地址
解析
調(diào)用目標(biāo)在程序代碼寫好、 編譯器進(jìn)行編譯時(shí)就必須確定下來。 這類方法的調(diào)用稱為解析(Resolution),解析的是非虛方法。
Java中5種方法調(diào)用字節(jié)碼指令
- invokestatic:調(diào)用靜態(tài)方法。
- invokespecial:調(diào)用實(shí)例構(gòu)造器<init>方法、 私有方法和父類方法。
- invokevirtual:調(diào)用所有的虛方法。
- invokeinterface:調(diào)用接口方法,會(huì)在運(yùn)行時(shí)再確定一個(gè)實(shí)現(xiàn)此接口的對象。
- invokedynamic:先在運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法,然后再執(zhí)行該方法,在此之前的4條調(diào)用指令,分派邏輯是固化在Java虛擬機(jī)內(nèi)部的,而invokedynamic指令的分派邏輯是由用戶所設(shè)定的引導(dǎo)方法決定的。
- 非虛方法
- 只要能被invokestatic和invokespecial指令調(diào)用的方法,都可以在解析階段中確定唯一的調(diào)用版本,符合這個(gè)條件的有靜態(tài)方法、 私有方法、 實(shí)例構(gòu)造器、 父類方法4類(還有final方法,但是是用invokevirtual調(diào)用),它們在類加載的時(shí)候就會(huì)把符號引用解析為該方法的直接引用。
分派
- 1.靜態(tài)分派(屬于多分派)
- 所有依賴靜態(tài)類型來定位方法執(zhí)行版本的分派動(dòng)作稱為靜態(tài)分派。
- 靜態(tài)類型的變化僅僅在使用時(shí)發(fā)生,變量本身的靜態(tài)類型不會(huì)被改變,并且最終的靜態(tài)類型是在編譯期可知的;而實(shí)際類型變化的結(jié)果在運(yùn)行期才可確定,編譯器在編譯程序的時(shí)候并不知道一個(gè)對象的實(shí)際類型是什么。 (Human human = new Man(),Human是靜態(tài)類型,Man是實(shí)例類型)
- 選擇重載版本的過程是通過靜態(tài)分派完成的。
- 2.動(dòng)態(tài)分派(屬于單分派)
- 在運(yùn)行期根據(jù)實(shí)際類型確定方法執(zhí)行版本的分派過程稱為動(dòng)態(tài)分派。
- 多次調(diào)用中的invokevirtual指令把常量池中的類方法符號引用解析到了不同的直接引用上,這個(gè)過程就是Java語言中方法重寫的本質(zhì)。
- 3.單分派與多分派
- 宗量:方法的接收者與方法的參數(shù)統(tǒng)稱為方法的宗量
- 單分派是根據(jù)一個(gè)宗量對目標(biāo)方法進(jìn)行選擇,多分派則是根據(jù)多于一個(gè)宗量對目標(biāo)方法進(jìn)行選擇。
- Java語言是一門靜態(tài)多分派、 動(dòng)態(tài)單分派的語言。
- 宗量:方法的接收者與方法的參數(shù)統(tǒng)稱為方法的宗量
- 4.虛擬機(jī)動(dòng)態(tài)分派的實(shí)現(xiàn)
類在方法區(qū)中建立一個(gè)虛方法表(Vritual Method Table,也稱為vtable,與此對應(yīng)的,在invokeinterface執(zhí)行時(shí)也會(huì)用到接口方法表——Inteface Method Table,簡稱itable),使用虛方法表索引來代替元數(shù)據(jù)查找以提高性能。
虛方法表中存放著各個(gè)方法的實(shí)際入口地址。 如果某個(gè)方法在子類中沒有被重寫,那子類的虛方法表里面的地址入口和父類相同方法的地址入口是一致的,都指向父類的實(shí)現(xiàn)入口。 如果子類中重寫了這個(gè)方法,子類方法表中的地址將會(huì)替換為指向子類實(shí)現(xiàn)版本的入口地址。
具有相同簽名的方法,在父類、 子類的虛方法表中都應(yīng)當(dāng)具有一樣的索引序號
方法表一般在類加載的連接階段進(jìn)行初始化,準(zhǔn)備了類的變量初始值后,虛擬機(jī)會(huì)把該類的方法表也初始化完畢。
在條件允許的情況下,還會(huì)使用內(nèi)聯(lián)緩存(Inline Cache)和基于“類型繼承關(guān)系分析”(Class Hierarchy Analysis,CHA)技術(shù)的守護(hù)內(nèi)聯(lián)(Guarded Inlining)兩種非穩(wěn)定的“激進(jìn)優(yōu)化”手段來獲得更高的性能
基于棧的字節(jié)碼解釋執(zhí)行引擎
Java語言中,Javac編譯器完成了程序代碼經(jīng)過詞法分析、 語法分析到抽象語法樹,再遍歷語法樹生成線性的字節(jié)碼指令流的過程。 因?yàn)檫@一部分動(dòng)作是在Java虛擬機(jī)之外進(jìn)行的,而解釋器在虛擬機(jī)的內(nèi)部,所以Java程序的編譯就是半獨(dú)立的實(shí)現(xiàn)。