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ī)棧中出棧入棧的過程.
棧幀:則是包含有局部變量表, 操作數(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空間就無法給其他變量使用, 因此, 最好是在需要使用到變量時(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方法不能夠被重寫.
方法分派(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)分派 與 重寫息息相關(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)在的問題在于, 虛擬機(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不用多說, 之前提到過, 是執(zhí)行 構(gòu)造器方法時(shí) 的指令
而 invokevirtual 則正是執(zhí)行 main.sayHello(), 方法的指令, 指令的運(yùn)行時(shí)解析過程大致如下:
而其中的關(guān)鍵點(diǎn)就在于, 取到的是 對(duì)象的實(shí)際類型.
1 找到操作數(shù)棧頂?shù)牡谝粋€(gè)元素的所指對(duì)象的實(shí)際類型, 記做C
2 如果在C中找到與描述符 和 簡(jiǎn)單名稱都相符的方法, 進(jìn)行訪問校驗(yàn), 如果可以則返回方法的直接引用, 否則拋出 IllegalAccessError異常
3 否則按照繼承關(guān)系 從下向上對(duì)C的各個(gè)父類進(jìn)行第二步的搜索驗(yàn)證過程.
4 如果始終找不到, 拋出異常.
動(dòng)態(tài)類型語言
這也是要提到的關(guān)于 invokedynamic指令的主要目的。
動(dòng)態(tài)類型語言的概念是: 意思就是類型的檢查是在運(yùn)行時(shí)做的而非編譯期。
而Java本身則是靜態(tài)類型語言, 這一點(diǎn)又在哪里能夠體現(xiàn)呢?
obj.println('language');
如果處在java環(huán)境中,且obj的靜態(tài)語言類型是 java.io.PrintStream, 那么obj本身的實(shí)際類型也必須是PrintStream的子類才行, 哪怕本身存在 println方法也不可以, 但同樣的問題放在 javascript中就不同了, 只要實(shí)際類型中存在println方法, 執(zhí)行就不會(huì)有任何問題.
這點(diǎn)就是因?yàn)? java在編譯時(shí)已經(jīng)將其完整的符號(hào)引用生成出來, 如果注意到的話, 會(huì)發(fā)現(xiàn)無論是動(dòng)態(tài)分派還是靜態(tài)分派, 在編譯的指令中都是已經(jīng)精確到相應(yīng)類的某一個(gè)方法中了, 如此, 自然只能夠在有限的范圍內(nèi)略做調(diào)整, 如果超出了當(dāng)前類的范圍, 就無法調(diào)用了.
jvm虛擬機(jī)并不僅僅是java語言的虛擬機(jī), 那么如何為動(dòng)態(tài)類型語言提供支持就是一個(gè)問題了, 并且在目前java8中的lamda表達(dá)式中也應(yīng)用的是 invokedynamic指令.
MethodHandle
而與之相關(guān)的jar包則是 java.lang.invoke, 相關(guān)的類則是 MethodHandle.
在這里我也并不想再談 MethodHandle的使用方法, 網(wǎng)上資料實(shí)在不少.
需要提到的是, 它的功能和java的反射略有相似, 通過方法名, class, 就可以調(diào)用相應(yīng)的方法. 但它比起反射要輕量級(jí); 且Reflection是在模擬Java代碼的調(diào)用, MethodHandle是在模仿字節(jié)碼層面的調(diào)用.
這個(gè)方法不失為是在動(dòng)態(tài)調(diào)用中除了反射之外的另一種選擇.
基于棧解釋器的執(zhí)行過程
其實(shí)本文更像是在 前一篇博客中 java內(nèi)存區(qū)域中的虛擬機(jī)棧的一種補(bǔ)充說明.
而真實(shí)的執(zhí)行流程, 我想通過下文的代碼來看:
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開始) //此時(shí):棧: - 局部變量表: slot1 100 2: istore_1 //與上面類似,重復(fù)過程 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í)行引擎正是通過這樣出棧入棧的方式完成指令, 而基于寄存器的則不然, 是將操作數(shù)存入寄存器, 同時(shí)將輸入值也就是指令參數(shù) 與 某寄存器的存儲(chǔ)值相加. 區(qū)別就在于存儲(chǔ)位置, 以及參數(shù)問題, 基于棧的大部分指令都是無參數(shù)指令, 指令很明確的規(guī)定了 要用哪幾個(gè)棧元素, 棧元素的類型是什么.
我們平常所使用的電腦, 其 X86指令集, 正是基于寄存器的指令集.
優(yōu)缺點(diǎn)則是: 基于棧, 可移植性較強(qiáng), 但速度比較慢, 慢的原因一是需要許多冗余操作, 代碼. 二是基于棧是基于內(nèi)存的操作方式, 而內(nèi)存的速度比起寄存器更是要慢上許多.
總結(jié)
本文大致介紹這樣幾點(diǎn):
java多態(tài)在 jvm層次的實(shí)現(xiàn).
為什么說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. python中scrapy處理項(xiàng)目數(shù)據(jù)的實(shí)例分析2. 快速搭建Spring Boot+MyBatis的項(xiàng)目IDEA(附源碼下載)3. js抽獎(jiǎng)轉(zhuǎn)盤實(shí)現(xiàn)方法分析4. IntelliJ IDEA導(dǎo)入jar包的方法5. Python requests庫(kù)參數(shù)提交的注意事項(xiàng)總結(jié)6. GIT相關(guān)-IDEA/ECLIPSE工具配置的教程詳解7. 教你在 IntelliJ IDEA 中使用 VIM插件的詳細(xì)教程8. 深入分析PHP設(shè)計(jì)模式9. 如何基于Python實(shí)現(xiàn)word文檔重新排版10. vue-electron中修改表格內(nèi)容并修改樣式
