8.1概述
執(zhí)行引擎:輸入字節(jié)碼文件糊秆,處理過程是字節(jié)碼解析的等效過程武福,輸出的是執(zhí)行結(jié)果
8.2運(yùn)行時(shí)棧幀結(jié)構(gòu)
棧幀是虛擬機(jī)用于方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的虛擬機(jī)棧的棧元素
棧幀存儲了方法的局部變量表扩然、操作數(shù)棧艘儒、動態(tài)連接、方法返回地址等信息
每一個方法從調(diào)用開始到執(zhí)行結(jié)束夫偶,就是棧幀在虛擬機(jī)棧中入棧出棧的過程
在編譯期間界睁,棧幀需要多大的局部變量表、多深的操作數(shù)棧都已經(jīng)完全確定兵拢,并且寫入方法的code 屬性中
一個棧幀需要分配多大內(nèi)存翻斟,不會受到程序運(yùn)行期變量數(shù)據(jù)影響
在活動線程中,棧頂?shù)臈攀怯行У乃盗澹Q為當(dāng)前棧幀访惜,相關(guān)聯(lián)的方法稱為當(dāng)前方法
8.2.1局部變量表
是一組變量值存儲空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量
在Java程序編譯為class時(shí)腻扇,就在方法的code屬性的max_locals數(shù)據(jù)項(xiàng)中確定了該方法所需分配的局部變量表的最大容量
局部變量表的容量以變量槽slot為最小單位
一個slot可以存放一個32位以內(nèi)的數(shù)據(jù)债热,Java中32位以內(nèi)的數(shù)據(jù)類型有:boolean、byte幼苛、char窒篱、short、int舶沿、float、reference、returnAddress
通過Reference類型可以:
從此引用直接或者間接的獲取到對象在堆上存放的起始地址索引
此引用中直接或者間接的查找到對象所屬數(shù)據(jù)類型在方法區(qū)的存儲類型信息
對于64位(long畸冲,double)的數(shù)據(jù)類型,虛擬機(jī)采用高位對齊的方式為其分配兩個slot空間
虛擬機(jī)通過索引定位的方式使用局部變量表
方法執(zhí)行過程中召夹,虛擬機(jī)通過局部變量表完成變量值到參數(shù)列表的傳遞過程
對于實(shí)例方法岩喷,局部變量表中第0位索引的slot默認(rèn)傳遞方法所屬對象實(shí)例的引用
為了節(jié)省棧幀空間,slot空間可以重用,但是會有額外的副作用,例如影響垃圾收集
8.2.2操作數(shù)棧(操作棧)
是一個后入先出棧
在編譯時(shí)候?qū)懭隿ode屬性的max_stacks數(shù)據(jù)項(xiàng)中
操作數(shù)棧的每一個元素可以是任意的Java數(shù)據(jù)類型
32位數(shù)據(jù)類型占用的棧容量為1,64位數(shù)據(jù)類型占用的棧容量為2
操作數(shù)棧中元素的數(shù)據(jù)類型和字節(jié)碼指令的序列嚴(yán)格匹配
概念模型上渔扎,兩個棧幀完全相互獨(dú)立,但大多虛擬機(jī)做了優(yōu)化處理残吩,使兩個棧幀出現(xiàn)一部分重疊(方法調(diào)用可以共用一部分?jǐn)?shù)據(jù),無需進(jìn)行額外的參數(shù)復(fù)制傳遞)
8.2.3動態(tài)連接
每個棧幀都有一個指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用绰疤,持有這個引用是為了支持方法調(diào)用過程中的動態(tài)連接
8.2.4方法返回地址
正常完成出口:PC計(jì)數(shù)器的值可以作為返回地址榨了,棧幀中很可能保存這個計(jì)數(shù)值
異常完成出口:通過異常處理器表來確定返回地址呐粘,棧幀中一般不會保存這部分信息
方法退出等同于當(dāng)前棧幀出棧五芝,可能執(zhí)行的操作有:
恢復(fù)上層方法的局部變量表和操作數(shù)棧渐尿,把返回值(如果有的話)壓入調(diào)用者棧幀的操作數(shù)棧中殴穴,調(diào)整PC計(jì)數(shù)器的值以指向方法調(diào)用指令后面的一條指令
8.2.5附加信息
8.3方法調(diào)用
方法調(diào)用不等同于方法執(zhí)行,唯一任務(wù)就是確定被調(diào)用方法的版本
8.3.1解析
調(diào)用目標(biāo)在程序?qū)懞米鸩小⒕幾g器進(jìn)行編譯時(shí)就必須確定下來顷扩,這類方法的調(diào)用稱為解析
編譯期可知汹胃,運(yùn)行期不變
靜態(tài)方法
私有方法
相對應(yīng)的5條方法調(diào)用字節(jié)碼指令
invokestatic:調(diào)用靜態(tài)方法
invokespecial:調(diào)用實(shí)例構(gòu)造器init方法、私有方法、父類方法
invokevirtual:調(diào)用所有的虛方法
invokeinterface:調(diào)用接口方法挪拟,會在運(yùn)行時(shí)再確定一個實(shí)現(xiàn)此接口的對象
invokedynamic:現(xiàn)在運(yùn)行時(shí)動態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法耘子,然后再執(zhí)行該方法
只要能被invokestatic和invokespecial指令調(diào)用的方法果漾,都可以再解析階段中確定唯一的調(diào)用版本球切,符合這個條件的由靜態(tài)方法谷誓、構(gòu)造方法、私有方法吨凑、父類方法四大類捍歪,這些方法稱為非虛方法(還包含final修飾的方法,無法被覆蓋鸵钝,沒有其他版本)糙臼;與之相反的稱為虛方法(final修飾除外)
8.3.2分派
靜態(tài)分派
靜態(tài)類型(外觀類型):變量本身的靜態(tài)類型不會被改變,最終的靜態(tài)類型是在編譯期可知的
實(shí)際類型:變化結(jié)果在運(yùn)行期才確定
使用哪個版本的重載恩商,完全取決于傳入?yún)?shù)的數(shù)量和數(shù)據(jù)類型变逃;虛擬機(jī)(準(zhǔn)確的說是編譯器)在重載時(shí)是通過參數(shù)的靜態(tài)類型而不是實(shí)際類型作為判定依據(jù);并且靜態(tài)類型是編譯期可知的怠堪,因此揽乱,在編譯階段,javac編譯器會根據(jù)參數(shù)的靜態(tài)類型決定使用哪個版本的重載
所有依賴靜態(tài)類型來定位方法執(zhí)行版本的分派稱為靜態(tài)分派
靜態(tài)分派的典型應(yīng)用就是方法重載
動態(tài)分派
重寫
invokevirtual的運(yùn)行時(shí)解析過程:
找到操作數(shù)棧頂?shù)牡谝粋€元素指向的對象的實(shí)際類型粟矿,記作C
如果類型C中找到與常量中的描述符和簡單名稱都相符的方法凰棉,則進(jìn)行訪問權(quán)限校驗(yàn),如果通過則返回這個方法的直接引用陌粹,查找過程結(jié)束撒犀,如果不存在,則返回java.lang.IllegalAccessError異常
否則掏秩,按照繼承關(guān)系從下往上依次對C的各個父類進(jìn)行第二步的查搜索和驗(yàn)證過程
如果始終沒有找到或舞,則拋出java.lang.AbstractMethodError異常
運(yùn)行期根據(jù)實(shí)際類型確定方法版本的分派稱為動態(tài)分派
單分派和多分派
方法的接受者方法的參數(shù)統(tǒng)稱為方法的宗量,根據(jù)分派基于多少種宗量蒙幻,可以將分派劃分為單分派和多分派
單分派是根據(jù)一個宗量對目標(biāo)方法進(jìn)行選擇
多分派是根據(jù)多于一個宗量對目標(biāo)方法進(jìn)行分派
靜態(tài)分派屬于多分派
動態(tài)分派屬于單分派
虛擬機(jī)動態(tài)分派的實(shí)現(xiàn)
方法表
8.3.3動態(tài)類型語言支持
動態(tài)類型語言
關(guān)鍵特征是類型檢查的主體過程是在運(yùn)行期而不是編譯期
變量無類型而變量值有類型
靜態(tài)類型語言在編譯期提供嚴(yán)謹(jǐn)?shù)念愋蜋z查
動態(tài)類型語言提供了更大的靈活性
JDK7與動態(tài)類型語言
invokedynamic指令以及java.lang.invoke包出現(xiàn)
java.lang.invoke包
提供了一種動態(tài)確定目標(biāo)方法的機(jī)制映凳,稱為MethodHandle
MethodHandle和反射(Reflection)區(qū)別:
本質(zhì)上將都是在模擬方法調(diào)用,但反射模擬Java代碼層次的調(diào)用杆煞,MethodHandle模擬字節(jié)碼層次的調(diào)用
反射中的java.lang.Method對象遠(yuǎn)比MethodHandle機(jī)制的java.lang.MethodHandle對象所彪悍的信息多魏宽,反射是重量級,MethodHandle是輕量級
MethodHandle優(yōu)化
invokedynamic指令
每一處含有invokedynamic指令的地方都稱為動態(tài)調(diào)用點(diǎn)
掌控方法分派規(guī)則
8.4基于棧的字節(jié)碼解釋執(zhí)行引擎
8.4.1解釋執(zhí)行
javac編譯器完成了程序代碼經(jīng)過詞法分析决乎、語法分析到抽象語法樹队询,在遍歷語法樹生成線性的字節(jié)碼指令流的過程
一部分在虛擬機(jī)之外進(jìn)行,而解釋器是在虛擬機(jī)內(nèi)部构诚,所以Java程序的編譯是半獨(dú)立的實(shí)現(xiàn)
8.4.2基于棧的指令集和基于寄存器的指令集
基于棧的指令集主要優(yōu)點(diǎn)是可移植性蚌斩、代碼更加緊湊、編譯實(shí)現(xiàn)更簡單范嘱;缺點(diǎn)是執(zhí)行速度相對慢點(diǎn)
8.4.3基于棧的解釋器執(zhí)行過程
參考文獻(xiàn):
[1] 深入理解Java虛擬機(jī) 第二版 --周志明