虛擬機字節(jié)碼執(zhí)行引擎
執(zhí)行引擎在執(zhí)行Java代碼的時候可能會有解釋執(zhí)行(通過解釋器執(zhí)行)和編譯執(zhí)行(通過即時編譯器產(chǎn)生本地代碼執(zhí)行)兩種選擇崎弃,也可能兩者兼?zhèn)湔耄踔吝€可能會包含幾個不同級別的編譯器執(zhí)行引擎。
運行時棧幀結(jié)構(gòu)
棧幀存儲了方法的局部變量表辑畦、 操作數(shù)棧、 動態(tài)連接和方法返回地址等信息。 (詳情見《深入理解Java虛擬機》(學(xué)習(xí)筆記(一)))
在活動線程中稽屏,只有位于棧頂?shù)臈攀怯行У模Q為當(dāng)前棧幀(Current StackFrame)西乖,與這個棧幀相關(guān)聯(lián)的方法稱為當(dāng)前方法(Current Method)狐榔。
局部變量表
是一組變量值存儲空間坛增,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。
局部變量表的容量以變量槽(Variable Slot薄腻,下稱Slot)為最小單位
如果執(zhí)行的是實例方法(非static的方法)收捣,那局部變量表中第0位索引的Slot默認(rèn)是用于傳遞方法所屬對象實例的引用,在方法中可以通過關(guān)鍵字“this”來訪問到這個隱含的參數(shù)庵楷。 其余參數(shù)則按照參數(shù)表順序排列罢艾,占用從1開始的局部變量Slot,參數(shù)表分配完畢后尽纽,再根據(jù)方法體內(nèi)部定義的變量順序和作用域分配其余的Slot咐蚯。
局部變量不存在“準(zhǔn)備階段”(詳情見《深入理解Java虛擬機》(學(xué)習(xí)筆記(五)))。如果一個局部變量定義了但沒有賦初始值是不能使用的弄贿。
操作數(shù)棧
是一個后入先出(Last In FirstOut,LIFO)棧
動態(tài)連接
每個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用春锋,持有這個引用是為了支持方法調(diào)用過程中的動態(tài)連接(Dynamic Linking)。
方法返回地址
當(dāng)一個方法開始執(zhí)行后差凹,只有兩種方式可以退出這個方法期奔。
- 第一種方式是執(zhí)行引擎遇到任意一個方法返回的字節(jié)碼指令,這時候可能會有返回值傳遞給上層的方法調(diào)用者(調(diào)用當(dāng)前方法的方法稱為調(diào)用者)危尿,是否有返回值和返回值的類型將根據(jù)遇到何種方法返回指令來決定呐萌,這種退出方法的方式稱為正常完成出口(Normal Method Invocation Completion)。
- 另外一種退出方式是谊娇,在方法執(zhí)行過程中遇到了異常搁胆,并且這個異常沒有在方法體內(nèi)得到處理,無論是Java虛擬機內(nèi)部產(chǎn)生的異常邮绿,還是代碼中使用athrow字節(jié)碼指令產(chǎn)生的異常渠旁,只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會導(dǎo)致方法退出船逮,這種退出方法的方式稱為異常完成出口(Abrupt Method Invocation Completion)顾腊。 一個方法使用異常完成出口的方式退出,是不會給它的上層調(diào)用者產(chǎn)生任何返回值的挖胃。
附加信息
虛擬機規(guī)范允許具體的虛擬機實現(xiàn)增加一些規(guī)范里沒有描述的信息到棧幀之中
方法調(diào)用
方法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用方法的版本
一切方法調(diào)用在Class文件里面存儲的都只是符號引用杂靶,而不是方法在實際運行時內(nèi)存布局中的入口地址
解析
調(diào)用目標(biāo)在程序代碼寫好、 編譯器進(jìn)行編譯時就必須確定下來酱鸭。 這類方法的調(diào)用稱為解析(Resolution)吗垮,解析的是非虛方法。
Java中5種方法調(diào)用字節(jié)碼指令
- invokestatic:調(diào)用靜態(tài)方法凹髓。
- invokespecial:調(diào)用實例構(gòu)造器<init>方法烁登、 私有方法和父類方法。
- invokevirtual:調(diào)用所有的虛方法蔚舀。
- invokeinterface:調(diào)用接口方法饵沧,會在運行時再確定一個實現(xiàn)此接口的對象锨络。
- invokedynamic:先在運行時動態(tài)解析出調(diào)用點限定符所引用的方法,然后再執(zhí)行該方法狼牺,在此之前的4條調(diào)用指令羡儿,分派邏輯是固化在Java虛擬機內(nèi)部的,而invokedynamic指令的分派邏輯是由用戶所設(shè)定的引導(dǎo)方法決定的是钥。
- 非虛方法
- 只要能被invokestatic和invokespecial指令調(diào)用的方法掠归,都可以在解析階段中確定唯一的調(diào)用版本,符合這個條件的有靜態(tài)方法悄泥、 私有方法虏冻、 實例構(gòu)造器、 父類方法4類(還有final方法码泞,但是是用invokevirtual調(diào)用),它們在類加載的時候就會把符號引用解析為該方法的直接引用狼犯。
分派
- 1.靜態(tài)分派(屬于多分派)
- 所有依賴靜態(tài)類型來定位方法執(zhí)行版本的分派動作稱為靜態(tài)分派余寥。
- 靜態(tài)類型的變化僅僅在使用時發(fā)生,變量本身的靜態(tài)類型不會被改變悯森,并且最終的靜態(tài)類型是在編譯期可知的宋舷;而實際類型變化的結(jié)果在運行期才可確定,編譯器在編譯程序的時候并不知道一個對象的實際類型是什么瓢姻。 (Human human = new Man()祝蝠,Human是靜態(tài)類型,Man是實例類型)
- 選擇重載版本的過程是通過靜態(tài)分派完成的幻碱。
- 2.動態(tài)分派(屬于單分派)
- 在運行期根據(jù)實際類型確定方法執(zhí)行版本的分派過程稱為動態(tài)分派绎狭。
- 多次調(diào)用中的invokevirtual指令把常量池中的類方法符號引用解析到了不同的直接引用上,這個過程就是Java語言中方法重寫的本質(zhì)褥傍。
- 3.單分派與多分派
- 宗量:方法的接收者與方法的參數(shù)統(tǒng)稱為方法的宗量
- 單分派是根據(jù)一個宗量對目標(biāo)方法進(jìn)行選擇儡嘶,多分派則是根據(jù)多于一個宗量對目標(biāo)方法進(jìn)行選擇。
- Java語言是一門靜態(tài)多分派恍风、 動態(tài)單分派的語言蹦狂。
- 宗量:方法的接收者與方法的參數(shù)統(tǒng)稱為方法的宗量
- 4.虛擬機動態(tài)分派的實現(xiàn)
類在方法區(qū)中建立一個虛方法表(Vritual Method Table,也稱為vtable朋贬,與此對應(yīng)的凯楔,在invokeinterface執(zhí)行時也會用到接口方法表——Inteface Method Table,簡稱itable)锦募,使用虛方法表索引來代替元數(shù)據(jù)查找以提高性能摆屯。
虛方法表中存放著各個方法的實際入口地址。 如果某個方法在子類中沒有被重寫糠亩,那子類的虛方法表里面的地址入口和父類相同方法的地址入口是一致的鸥拧,都指向父類的實現(xiàn)入口党远。 如果子類中重寫了這個方法,子類方法表中的地址將會替換為指向子類實現(xiàn)版本的入口地址富弦。
具有相同簽名的方法沟娱,在父類、 子類的虛方法表中都應(yīng)當(dāng)具有一樣的索引序號
方法表一般在類加載的連接階段進(jìn)行初始化腕柜,準(zhǔn)備了類的變量初始值后济似,虛擬機會把該類的方法表也初始化完畢。
在條件允許的情況下盏缤,還會使用內(nèi)聯(lián)緩存(Inline Cache)和基于“類型繼承關(guān)系分析”(Class Hierarchy Analysis,CHA)技術(shù)的守護內(nèi)聯(lián)(Guarded Inlining)兩種非穩(wěn)定的“激進(jìn)優(yōu)化”手段來獲得更高的性能
基于棧的字節(jié)碼解釋執(zhí)行引擎
Java語言中砰蠢,Javac編譯器完成了程序代碼經(jīng)過詞法分析、 語法分析到抽象語法樹唉铜,再遍歷語法樹生成線性的字節(jié)碼指令流的過程台舱。 因為這一部分動作是在Java虛擬機之外進(jìn)行的,而解釋器在虛擬機的內(nèi)部潭流,所以Java程序的編譯就是半獨立的實現(xiàn)竞惋。