虛擬機(jī)的執(zhí)行引擎可以指定指令集深胳,執(zhí)行那些不能夠被硬件系統(tǒng)直接支持的指令集格式绰疤。
Engine執(zhí)行代碼時(shí)一般分為兩種類型:
- 解釋執(zhí)行 傳統(tǒng)方式
- 編譯執(zhí)行(e.g JIT),產(chǎn)生本地機(jī)器碼舞终,編譯花費(fèi)時(shí)間多轻庆,但是執(zhí)行時(shí)效率和速度更高
棧幀
在線程的JVM Stack中使用,用于支持JVM進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)
- 包括了局部變量表敛劝、方法返回地址余爆、操作數(shù)棧、動態(tài)鏈接和附加信息夸盟。由于在編譯Java時(shí)蛾方,需要多大空間的局部變量表和操作數(shù)棧已經(jīng)確定,因此棧幀占用的內(nèi)存不會受到運(yùn)行時(shí)間變量數(shù)據(jù)的影響。
- 對Engine來說桩砰,在活動線程中拓春,只有處于棧頂?shù)臈–urrent Stack Frame)才是有效的,所有的字節(jié)碼指令都只對都只作用在Current Stack Frame關(guān)聯(lián)的方法 (Current Method).
局部變量表
一組變量值存儲空間五芝,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量痘儡。 局部變量的容量以變量槽(Slot)為最小單位。
- 每一個Slot占用的內(nèi)存空間并沒有指定枢步,根據(jù)不同平臺的JVM而已。但一般都可以存放32位以內(nèi)的數(shù)據(jù)類型:boolean(1), byte(8), char(16), short(16), int(32), float(32), reference (對象引用) 和 returnAddress渐尿。對于reference, JVM必須要做到能夠直接或間接查找到對象在Java堆中的數(shù)據(jù)存放地址起始索引和查找到對象所屬數(shù)據(jù)類型在方法區(qū)中的存儲的類型信息醉途。
- 在方法執(zhí)行時(shí),JVM使用局部變量表完成 參數(shù)值 到 參數(shù)變量列表 的傳遞砖茸。第0位索引的Slot默認(rèn)是用于傳遞方法所屬對象實(shí)例的引用(this)隘擎,其余參數(shù)則按照參數(shù)表順序排列,后面再根據(jù)方法體內(nèi)部定義的變量順序和作用域分配其余的Slot.
操作數(shù)棧
LIFO棧凉夯,操作數(shù)棧中的每一個元素可以是在任意的Java數(shù)據(jù)類型货葬,32位的占用棧容量1,64位的占用棧容量2劲够。Engine執(zhí)行過程其實(shí)就是從操作數(shù)棧從提取元素然后執(zhí)行指令震桶,然后再將執(zhí)行的結(jié)果壓入棧中。
動態(tài)鏈接
每一個棧幀都包含有一個指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用征绎,持有這個引用是為了支持方法調(diào)用過程中的動態(tài)鏈接蹲姐。
字節(jié)碼的方法調(diào)用指令就是以Class文件的常量池中指向方法的符號引用作為參數(shù);這些符號引用在類加載或者第一次使用的時(shí)候就轉(zhuǎn)為了直接引用人柿,這種方式稱為靜態(tài)解析柴墩;而有點(diǎn)符號鏈接會在每一次運(yùn)行期間轉(zhuǎn)化為直接引用,稱為動態(tài)鏈接凫岖。
方法返回地址
一個方法執(zhí)行完成之后江咳,只有兩種方式可以退出這個方法。第一種方式是遇到任意一個方法返回的字節(jié)碼指令哥放,然后將返回值(如果有的話)返回給上層的方法調(diào)用者歼指,這種方式稱為Normal Method Invocation Completion;而另外一種方式是在方法執(zhí)行過程中遇到了異常婶芭,同時(shí)異常沒有在方法體內(nèi)部得到處理從而導(dǎo)致方法退出东臀,該種方式稱為Abrupt Method Invocation Completion。
方法退出的過程實(shí)際上就是將當(dāng)前棧幀出棧犀农,因此退出時(shí)可能的操作有:恢復(fù)上層方法的局部變量表和操作數(shù)棧惰赋,把返回值壓入調(diào)用者棧幀的操作數(shù)棧中,調(diào)用PC計(jì)數(shù)器的值以指向方法調(diào)用指令后面的一條指令。
方法調(diào)用
由上面提到的靜態(tài)解析我們得知赁濒,有一些方法調(diào)用指令的符號引用在類加載或者第一次使用時(shí)就已經(jīng)轉(zhuǎn)化為了直接引用轨奄,這種方法基于一個前提:方法在真正運(yùn)行之前就有一個可確定的調(diào)用版本,并且該版本在運(yùn)行時(shí)是不會改變的拒炎。
這種方法在JVM規(guī)范里也稱為“非虛方法”挪拟,符合這個前提條件的方法有:static修飾的靜態(tài)方法、private 修飾的私有方法击你、類的實(shí)例構(gòu)造器<init>玉组、父類方法和有final 修飾的方法。
其根本原因是這些方法在程序運(yùn)行期間不能被通過任何方式重寫為其他的版本丁侄,因此在編譯期間便可以將他們確定下來惯雳。
Method Overload Resolution
- Static
當(dāng)我們在Java中聲明一個引用時(shí)會給他附上一個類型,該類型稱為變量的Static Type鸿摇,而當(dāng)使用某個Class(類型)實(shí)例為一個對象時(shí)石景,該類型也稱之為變量的Actual Type。由于在編譯時(shí)拙吉,程序代碼已經(jīng)確定潮孽,因此Static Type在編譯期是可知的(聲明變量,強(qiáng)制類型轉(zhuǎn)換)筷黔;但是Actual Type在運(yùn)行時(shí)才能夠確定下來往史。
因此JVM的Compiler在進(jìn)行Method Overloading時(shí)是以參數(shù)的靜態(tài)類型作為判斷依據(jù)的。 - Dynamic
Java的多態(tài)性核心之一重載的本質(zhì)我們已經(jīng)在上面的靜態(tài)分派中有所了解必逆,多態(tài)的另一個重要功能——重寫怠堪,便同動態(tài)分派息息相關(guān)。
通過觀察Class的字節(jié)碼名眉,可以發(fā)現(xiàn)粟矿,在執(zhí)行方法時(shí),JVM會先從操作數(shù)棧中提取出方法的執(zhí)行者(Receiver)损拢,然后在其之上調(diào)用invokevirtual指令(將指向Class文件常量池中的方法符號引用解析到不同的直接引用上)陌粹。
因此,真正執(zhí)行方法的版本其實(shí)是根據(jù)Receiver來確定的福压,換句話說就是根據(jù)對象的實(shí)際類型來確定掏秩。
第一步先確認(rèn)方法執(zhí)行者Receiver的實(shí)際類型,在第二步進(jìn)行方法符號引用解析時(shí)根據(jù)Receiver實(shí)際類型的不同將符號引用解析到不同的直接引用上荆姆。這既是虛方法的調(diào)用過程蒙幻,亦是Java語言方法重寫(Method Overwriting)的本質(zhì)。