Java虛擬機(jī)執(zhí)行引擎知識(shí)總結(jié)
執(zhí)行引擎
也只有幾個(gè)概念, JVM方法調(diào)用和執(zhí)行的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)是 棧幀, 是內(nèi)存區(qū)域中 虛擬機(jī)棧中的棧元素, 每一個(gè)方法的執(zhí)行就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中出棧入棧的過(guò)程.
棧幀:則是包含有局部變量表, 操作數(shù)棧, 動(dòng)態(tài)連接, 方法返回地址, 附加信息.
1 局部變量表:
存儲(chǔ)單位是 slot, 一個(gè)slot占據(jù)32位, 對(duì)于64位的數(shù)據(jù)類型, 則是分配連續(xù)兩個(gè)slot空間. 而對(duì)于一個(gè)非靜態(tài)方法而言, 有一個(gè)隱藏參數(shù), 為 this, 而在局部變量表中的變量存儲(chǔ)順序則是
this -> 方法參數(shù) -> 方法體內(nèi)的變量(slot可以重用, 超出作用域即可復(fù)用.) 方法在編譯完成后, 其所需的空間已經(jīng)確定.
(這里也是需要注意的一個(gè)地方, 變量的作用域常常會(huì)覆蓋整個(gè)方法, 即使變量已經(jīng)不再使用, 但只要還在作用域內(nèi), 其slot空間就無(wú)法給其他變量使用, 因此, 最好是在需要使用到變量時(shí), 定義在合理的作用域范圍內(nèi).)
2 操作數(shù)棧:
在操作數(shù)棧中需要注意,其數(shù)據(jù)類型必須與字節(jié)碼指令的序列嚴(yán)格匹配.
3 動(dòng)態(tài)連接: 稍后詳解
4 方法返回地址:
方法有兩種退出方式, 正常退出, 異常退出, 當(dāng)正常退出后, 會(huì)恢復(fù)上層方法的局部變量表, 操作數(shù)棧, 并把方法返回結(jié)果壓入調(diào)用者的操作數(shù)棧.
方法調(diào)用
方法調(diào)用階段的唯一目的是, 確定調(diào)用方法的版本究竟是哪一個(gè).
在Java虛擬機(jī)中提供了5條方法調(diào)用的相關(guān)指令:
invokestatic: 調(diào)用靜態(tài)方法
invokespecial: 調(diào)用實(shí)例構(gòu)造器方法, 私有方法, 父類方法
invokevirtual: 調(diào)用所有的虛方法
invokeinterface: 調(diào)用所有的接口方法
invokedynamic: 先在運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法, 然后再執(zhí)行該方法.
虛方法是非虛方法的補(bǔ)集, 什么是非虛方法呢? 能夠在編譯器就確定將要調(diào)用的究竟是哪個(gè)方法, 進(jìn)而將該方法的符號(hào)引用 轉(zhuǎn)換為 相應(yīng)的直接引用的 方法就被稱作非虛方法.
我們知道在類加載時(shí), 在相應(yīng)的類信息中, 存有對(duì)應(yīng)方法的相關(guān)信息, 常量池中存有相關(guān)直接引用. 在類加載的解析階段, 即會(huì)將這部分的符號(hào)引用轉(zhuǎn)換為直接引用.
那么什么方法才滿足這種條件呢?
能夠被invokespecial 和 invokestatic指令調(diào)用的方法, 都是可以在編譯器確定的方法, 即靜態(tài)方法, 私有方法, 父類方法(super.), 實(shí)例構(gòu)造器.
在final方法是個(gè)特殊點(diǎn), 雖然final方法的執(zhí)行為 invokevirtual, 但它依然屬于非虛方法, 不難理解, final方法不能夠被重寫(xiě).
方法分派(dispatch)
1 靜態(tài)分派
對(duì)于代碼
Human man = new Man();
其中Human被稱為變量的靜態(tài)類型, 也叫外觀類型, 而 Man則是變量的實(shí)際類型. 而一個(gè)變量的靜態(tài)類型, 在聲明時(shí)即已經(jīng)確定, 僅僅在使用時(shí)才能夠臨時(shí)轉(zhuǎn)換靜態(tài)類型, 但變量本身的靜態(tài)類型并不會(huì)改變, 實(shí)際類型的變化只有在運(yùn)行期才能確定.
//實(shí)際類型變化 Human man = new Man(); man = new Woman(); //靜態(tài)類型的變化 method((Man) man); method((Woman) man);
而當(dāng)我們?cè)谥剌d方法時(shí), 向方法中傳入的參數(shù)類型, 即是靜態(tài)類型.因此 重載是一種 可以在編譯期就被確定執(zhí)行方法版本 的行為.
2 動(dòng)態(tài)分派
動(dòng)態(tài)分派 與 重寫(xiě)息息相關(guān).
static class Human{ void sayHello() { System.out.println('human say hello'); } } static class Man extends Human{ @Override void sayHello() { System.out.println('man say hello'); } } void sayHello(Human man) { man.sayHello(); } public static void main(String[] args) { Human man = new Man(); Human human = new Human(); new Main().sayHello(man); new Main().sayHello(human); } //out: man say hello human say hello
結(jié)果不必多做解釋, 而現(xiàn)在的問(wèn)題在于, 虛擬機(jī)如何知道, 究竟調(diào)用的是哪個(gè)方法?
0: new #3 // class Main$Man 3: dup 4: invokespecial #4 // Method Main$Man.'<init>':()V 7: astore_1 8: new #5 // class Main$Human 11: dup 12: invokespecial #6 // Method Main$Human.'<init>':()V 15: astore_2 16: new #7 // class Main 19: dup 20: invokespecial #8 // Method '<init>':()V 23: aload_1 24: invokevirtual #9 // Method sayHello:(LMain$Human;)V 27: new #7 // class Main 30: dup 31: invokespecial #8 // Method '<init>':()V 34: aload_2 35: invokevirtual #9 // Method sayHello:(LMain$Human;)V 38: return
其中主要關(guān)注幾個(gè)方法的執(zhí)行點(diǎn), invokespecial不用多說(shuō), 之前提到過(guò), 是執(zhí)行 構(gòu)造器方法時(shí) 的指令
而 invokevirtual 則正是執(zhí)行 main.sayHello(), 方法的指令, 指令的運(yùn)行時(shí)解析過(guò)程大致如下:
而其中的關(guān)鍵點(diǎn)就在于, 取到的是 對(duì)象的實(shí)際類型.
1 找到操作數(shù)棧頂?shù)牡谝粋€(gè)元素的所指對(duì)象的實(shí)際類型, 記做C
2 如果在C中找到與描述符 和 簡(jiǎn)單名稱都相符的方法, 進(jìn)行訪問(wèn)校驗(yàn), 如果可以則返回方法的直接引用, 否則拋出 IllegalAccessError異常
3 否則按照繼承關(guān)系 從下向上對(duì)C的各個(gè)父類進(jìn)行第二步的搜索驗(yàn)證過(guò)程.
4 如果始終找不到, 拋出異常.
動(dòng)態(tài)類型語(yǔ)言
這也是要提到的關(guān)于 invokedynamic指令的主要目的。
動(dòng)態(tài)類型語(yǔ)言的概念是: 意思就是類型的檢查是在運(yùn)行時(shí)做的而非編譯期。
而Java本身則是靜態(tài)類型語(yǔ)言, 這一點(diǎn)又在哪里能夠體現(xiàn)呢?
obj.println('language');
如果處在java環(huán)境中,且obj的靜態(tài)語(yǔ)言類型是 java.io.PrintStream, 那么obj本身的實(shí)際類型也必須是PrintStream的子類才行, 哪怕本身存在 println方法也不可以, 但同樣的問(wèn)題放在 javascript中就不同了, 只要實(shí)際類型中存在println方法, 執(zhí)行就不會(huì)有任何問(wèn)題.
這點(diǎn)就是因?yàn)? java在編譯時(shí)已經(jīng)將其完整的符號(hào)引用生成出來(lái), 如果注意到的話, 會(huì)發(fā)現(xiàn)無(wú)論是動(dòng)態(tài)分派還是靜態(tài)分派, 在編譯的指令中都是已經(jīng)精確到相應(yīng)類的某一個(gè)方法中了, 如此, 自然只能夠在有限的范圍內(nèi)略做調(diào)整, 如果超出了當(dāng)前類的范圍, 就無(wú)法調(diào)用了.
jvm虛擬機(jī)并不僅僅是java語(yǔ)言的虛擬機(jī), 那么如何為動(dòng)態(tài)類型語(yǔ)言提供支持就是一個(gè)問(wèn)題了, 并且在目前java8中的lamda表達(dá)式中也應(yīng)用的是 invokedynamic指令.
MethodHandle
而與之相關(guān)的jar包則是 java.lang.invoke, 相關(guān)的類則是 MethodHandle.
在這里我也并不想再談 MethodHandle的使用方法, 網(wǎng)上資料實(shí)在不少.
需要提到的是, 它的功能和java的反射略有相似, 通過(guò)方法名, class, 就可以調(diào)用相應(yīng)的方法. 但它比起反射要輕量級(jí); 且Reflection是在模擬Java代碼的調(diào)用, MethodHandle是在模仿字節(jié)碼層面的調(diào)用.
這個(gè)方法不失為是在動(dòng)態(tài)調(diào)用中除了反射之外的另一種選擇.
基于棧解釋器的執(zhí)行過(guò)程
其實(shí)本文更像是在 前一篇博客中 java內(nèi)存區(qū)域中的虛擬機(jī)棧的一種補(bǔ)充說(shuō)明.
而真實(shí)的執(zhí)行流程, 我想通過(guò)下文的代碼來(lái)看:
public int add() { int a = 100; int b = 200; int c = 300; return (a + b) * c;}-- javap -verbose Mainpublic int add();// 返回類型為 intdescriptor: ()Iflags: ACC_PUBLICCode://需要深度為2的操作數(shù)棧, 4個(gè)slot的局部變量空間, 有一個(gè)參數(shù)為 this stack=2, locals=4, args_size=1 //將100推入操作數(shù)棧頂, 棧:100 0: bipush 100 //將棧頂?shù)臄?shù)據(jù)出棧并存儲(chǔ)到局部變量表的第一個(gè)slot中(從0開(kāi)始) //此時(shí):棧: - 局部變量表: slot1 100 2: istore_1 //與上面類似,重復(fù)過(guò)程 3: sipush 200 6: istore_2 7: sipush 300 //此時(shí):棧: - 局部變量表: slot1 100 slot2 200 slot3 300 10: istore_3 //將局部變量表 slot1的值復(fù)制到 棧頂 11: iload_1 //將局部變量表 slot2的值復(fù)制到 棧頂 此時(shí):棧: 200 100 12: iload_2 //棧頂兩個(gè)元素出棧, 并相加, 結(jié)果重新入棧. 此時(shí): 棧: 300 13: iadd //將局部變量表 slot3的值復(fù)制到 棧頂 此時(shí):棧: 300 300 14: iload_3 //將棧頂元素相乘, 結(jié)果重新入棧 15: imul //將棧頂?shù)慕Y(jié)果返回給方法調(diào)用者. 方法執(zhí)行結(jié)束 16: ireturn LineNumberTable: line 85: 0 line 86: 3 line 87: 7 line 88: 11
基于棧的執(zhí)行引擎正是通過(guò)這樣出棧入棧的方式完成指令, 而基于寄存器的則不然, 是將操作數(shù)存入寄存器, 同時(shí)將輸入值也就是指令參數(shù) 與 某寄存器的存儲(chǔ)值相加. 區(qū)別就在于存儲(chǔ)位置, 以及參數(shù)問(wèn)題, 基于棧的大部分指令都是無(wú)參數(shù)指令, 指令很明確的規(guī)定了 要用哪幾個(gè)棧元素, 棧元素的類型是什么.
我們平常所使用的電腦, 其 X86指令集, 正是基于寄存器的指令集.
優(yōu)缺點(diǎn)則是: 基于棧, 可移植性較強(qiáng), 但速度比較慢, 慢的原因一是需要許多冗余操作, 代碼. 二是基于棧是基于內(nèi)存的操作方式, 而內(nèi)存的速度比起寄存器更是要慢上許多.
總結(jié)
本文大致介紹這樣幾點(diǎn):
java多態(tài)在 jvm層次的實(shí)現(xiàn).
為什么說(shuō)jvm執(zhí)行引擎是基于棧的執(zhí)行引擎, 以及究竟是怎樣一個(gè)流程.
以上就是Java虛擬機(jī)執(zhí)行引擎知識(shí)總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Java虛擬機(jī)執(zhí)行引擎的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!
相關(guān)文章:
1. .NET中l(wèi)ambda表達(dá)式合并問(wèn)題及解決方法2. JSP數(shù)據(jù)交互實(shí)現(xiàn)過(guò)程解析3. Python importlib動(dòng)態(tài)導(dǎo)入模塊實(shí)現(xiàn)代碼4. 利用promise及參數(shù)解構(gòu)封裝ajax請(qǐng)求的方法5. 淺談python出錯(cuò)時(shí)traceback的解讀6. python matplotlib:plt.scatter() 大小和顏色參數(shù)詳解7. windows服務(wù)器使用IIS時(shí)thinkphp搜索中文無(wú)效問(wèn)題8. ASP 信息提示函數(shù)并作返回或者轉(zhuǎn)向9. Nginx+php配置文件及原理解析10. 在Android中使用WebSocket實(shí)現(xiàn)消息通信的方法詳解
