在java虛擬機(jī)規(guī)范中定制了虛擬機(jī)字節(jié)碼執(zhí)行引擎的概念模型,這個(gè)概念模型成為各種虛擬機(jī)執(zhí)行引擎的統(tǒng)一外觀(Facade)。從外觀上看,所有java虛擬機(jī)的執(zhí)行引擎都是一致的:輸入字節(jié)碼文件诫睬,輸出執(zhí)行結(jié)果
運(yùn)行時(shí)幀棧結(jié)構(gòu)
棧幀(Stack Frame)是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),它是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)中的虛擬機(jī)棧的棧元素效五。典型棧幀結(jié)構(gòu):
局部變量表
局部變量表(Local Variable Table)是一組變量值存儲(chǔ)空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量炉峰。局部變量表的容量以變量槽(Variable Slot)為最小單位畏妖,虛擬機(jī)規(guī)范中并沒有明確指定一個(gè)Slot應(yīng)占用的內(nèi)存空間大小,只是規(guī)定每個(gè)Slot都應(yīng)該能存放一個(gè)boolean疼阔、byte戒劫、char、short婆廊、int迅细、float、reference或returnAddress類型的數(shù)據(jù)淘邻,這樣可以屏蔽32位跟64位虛擬機(jī)在內(nèi)存空間上的差異茵典。
虛擬機(jī)通過索引定位的方式使用局部變量表,索引值的范圍從0到最大Slot數(shù)量宾舅,索引n對(duì)應(yīng)第n個(gè)Slot统阿。局部變量表中第0位索引的Slot默認(rèn)是用于傳遞方法所屬對(duì)象實(shí)例的引用,即this筹我。
為了盡可能的節(jié)省棧幀空間砂吞,局部變量表中的Slot是可以重用的,同時(shí)這也影響了垃圾收集行為崎溃。即對(duì)已使用完畢的變量,局部變量表仍持有該對(duì)象的引用盯质,導(dǎo)致對(duì)象無法被GC回收袁串,占用大量內(nèi)存。這也是“不使用的對(duì)象應(yīng)手動(dòng)賦值為null”這條推薦編碼規(guī)則的原因呼巷。不過從執(zhí)行角度使用賦null值的操作來優(yōu)化內(nèi)存回收是建立在對(duì)字節(jié)碼執(zhí)行引擎概念模型的理解之上囱修,代碼在經(jīng)過編譯器優(yōu)化后才是虛擬機(jī)真正需要執(zhí)行的代碼,這時(shí)賦null值會(huì)被消除掉王悍,因此更優(yōu)雅的解決辦法是以恰當(dāng)?shù)淖兞孔饔糜?/strong>來控制變量回收時(shí)間破镰。
操作數(shù)棧
操作數(shù)棧(Operand Stack)也常稱操作棧,它是一個(gè)后入先出(Last In First Out,LIFO)棧。方法在執(zhí)行過程中鲜漩,通過各種字節(jié)碼指令對(duì)棧進(jìn)行操作源譬,出棧/入棧。java虛擬機(jī)的解釋執(zhí)行引擎稱為“基于棧的執(zhí)行引擎”孕似,其中所指的“棽饶铮”就是操作數(shù)棧
動(dòng)態(tài)連接
每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,持有這個(gè)引用時(shí)為了執(zhí)行方法調(diào)用過程中的動(dòng)態(tài)連接(Dynamic Linking)
方法返回地址
當(dāng)一個(gè)方法開始執(zhí)行后喉祭,只有兩種方式可以退出這個(gè)方法:
- 執(zhí)行引擎遇到任意一個(gè)方法返回的字節(jié)碼指令养渴,這個(gè)時(shí)候可能會(huì)有返回值傳遞給上層的方法調(diào)用者(調(diào)用當(dāng)前方法的方法稱為調(diào)用者),這種退出方式稱為正常完成出口(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)孵滞,這時(shí)不會(huì)給它的上層調(diào)用者產(chǎn)生任何返回值
方法退出的過程實(shí)際上就等同于把當(dāng)前棧幀出棧中捆,因此退出時(shí)可能執(zhí)行的操作有:
- 恢復(fù)上層方法的局部變量表和操作數(shù)棧
- 把返回值(如果有)壓入調(diào)用者棧幀的操作數(shù)棧
- 調(diào)整PC計(jì)數(shù)器的值以指向方法調(diào)用指令后面的一條指定等
附加信息
虛擬機(jī)規(guī)范允許具體的虛擬機(jī)實(shí)現(xiàn)增加一些規(guī)范里沒有描述的信息到棧幀中,稱之為棧幀信息
方法調(diào)用
方法調(diào)用并不等同于方法執(zhí)行坊饶,方法調(diào)用階段的唯一任務(wù)就是確定被調(diào)用方法的版本泄伪,即調(diào)用哪一個(gè)方法,暫時(shí)還不涉及方法內(nèi)部的具體運(yùn)行過程匿级,就是類加載過程中的類方法解析蟋滴。
解析
解析就是將Class的常量池中的符號(hào)引用轉(zhuǎn)化為直接引用。在java虛擬機(jī)中提供了5條方法調(diào)用字節(jié)碼指令:
-
invokestatic:調(diào)用靜態(tài)方法
System.exit(1); ==>編譯 iconst_1 ;將1放入棧內(nèi) ;執(zhí)行System.exit() invokestatic java/lang/System/exit(I)V
-
invokespecial:調(diào)用實(shí)例構(gòu)造器
<init>
方法痘绎、私有方法和父類方法//<init>方法 new StringBuffer() ==>編譯 new java/lang/StringBuffer ;創(chuàng)建一個(gè)StringBuffer對(duì)象 dup ;將對(duì)象彈出棧頂 ;執(zhí)行<init>()來初始化對(duì)象 invokespecial java/lang/StringBuffer/<init>()V //父類方法 super.equals(x); ==>編譯 aload_0 ;將this入棧 aload_1 ;將第一個(gè)參數(shù)入棧 ;執(zhí)行Object的equals()方法 invokespecial java/lang/Object/equals(Ljava/lang/Object;)Z //私有方法 與父類方法類似
-
invokevirtual:調(diào)用所有的虛方法
X x; ... x.equals("abc"); ==>編譯 aload_1 ;將x入棧 ldc "abc" ;將“abc”入棧 ;執(zhí)行equals()方法 invokevirtual X/equals(Ljava/lang/Object;)Z
-
invokeinterface:調(diào)用接口方法津函,會(huì)在運(yùn)行時(shí)再確定一個(gè)實(shí)現(xiàn)此接口的對(duì)象
List x; ... x.toString(); ==>編譯 aload_1 ;將x入棧 ;執(zhí)行toString()方法 invokeinterface java/util/List/toString()Z
invokedynamic:先在運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法,然后再執(zhí)行該方法
在編譯階段就可以確定唯一調(diào)用版本的方法有:靜態(tài)方法(類名)孤页、私有方法尔苦、實(shí)例構(gòu)造器(<init>
)、父類方法(super)行施、final方法允坚。其它統(tǒng)稱為虛方法,在編譯階段無法確定調(diào)用版本蛾号,需要在運(yùn)行期通過分派將符號(hào)引用轉(zhuǎn)變?yōu)橹苯右?/p>
分派
- 靜態(tài)分派:是指在運(yùn)行時(shí)對(duì)類內(nèi)相同名稱的方法根據(jù)描述符來確定執(zhí)行版本的分派稠项,即方法重載
- 動(dòng)態(tài)分派:是指對(duì)于相同方法簽名的方法根據(jù)實(shí)際執(zhí)行對(duì)象來確定執(zhí)行版本的分派。編譯器是根據(jù)引用類型來判斷方法是否可執(zhí)行鲜结,真正執(zhí)行的是實(shí)際對(duì)象方法
- 單分派與多分派:方法的接收者與方法的參數(shù)統(tǒng)稱為方法的宗量展运。單分派是根據(jù)根據(jù)一個(gè)宗量對(duì)方法進(jìn)行選擇活逆,多分派是根據(jù)多個(gè)宗量對(duì)方法進(jìn)行選擇
- 虛擬機(jī)動(dòng)態(tài)分派的實(shí)現(xiàn):由于動(dòng)態(tài)分派是非常頻繁的動(dòng)作,基于性能的考慮拗胜,虛擬機(jī)中最常用的“穩(wěn)定優(yōu)化”手段是為類在方法區(qū)中建立一個(gè)虛方法表(Virtual Method Table蔗候,與此對(duì)應(yīng)的,在invokeinterface執(zhí)行時(shí)也會(huì)用到接口方法表Interface Method Table)挤土,使用虛方法表索引來代替元數(shù)據(jù)查找以提高性能琴庵。除使用方法表外,還可以使用內(nèi)聯(lián)緩存(Inline Cache)和基于“類型繼承關(guān)系分析”(Class Hierarchy Analysis,CHA)技術(shù)的守護(hù)內(nèi)聯(lián)(Guarded Inlining)等
基于棧的字節(jié)碼解釋執(zhí)行引擎
解釋執(zhí)行
- 將上面步驟獨(dú)立于執(zhí)行引擎仰美,形成一個(gè)完整的編譯器 ==> C/C++
- 將其中一部分步驟實(shí)現(xiàn)為一個(gè)半獨(dú)立的編譯器 ==> Java
- 將上面步驟和執(zhí)行引擎全部集中封裝在一個(gè)封閉的黑匣子中 ==> JavaScript(大多數(shù)執(zhí)行器)
基于棧的指令集與基于寄存器的指令集
- 基于棧的指令集:
- 優(yōu)點(diǎn):可移植迷殿、代碼相對(duì)更緊湊、編譯器實(shí)現(xiàn)更簡單等
- 缺點(diǎn):執(zhí)行速度慢咖杂、完成相同功能的指令數(shù)量更多庆寺、棧位于內(nèi)存中
- 基于寄存器的指令集
- 優(yōu)點(diǎn):速度快
- 缺點(diǎn):與硬件結(jié)合緊密