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方法不能夠被重寫.
方法分派(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)在的問(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)注腳本之家其它相關(guān)文章!
- Java虛擬機(jī)之類加載
- java虛擬機(jī)之JVM調(diào)優(yōu)詳解
- Java虛擬機(jī)內(nèi)存區(qū)域劃分詳解
- 概述java虛擬機(jī)中類的加載器及類加載過(guò)程
- 深入了解Java虛擬機(jī)棧以及內(nèi)存模型
- 淺析Java虛擬機(jī)詳解之概述、對(duì)象生存法則
- Java跨平臺(tái)原理與虛擬機(jī)相關(guān)簡(jiǎn)介
- java虛擬機(jī)jvm方法區(qū)實(shí)例講解
- java虛擬機(jī)鉤子關(guān)閉函數(shù)addShutdownHook的操作
- java虛擬機(jī)是做什么用的
- 詳解Java 虛擬機(jī)垃圾收集機(jī)制
- Java虛擬機(jī)使用jvisualvm工具遠(yuǎn)程監(jiān)控tomcat內(nèi)存
- Java虛擬機(jī)常見(jiàn)內(nèi)存溢出錯(cuò)誤匯總
- Java啟用Azure Linux虛擬機(jī)診斷設(shè)置
- Java虛擬機(jī)內(nèi)存溢出與內(nèi)存泄漏
- java虛擬機(jī)創(chuàng)建失敗的原因整理
- Java內(nèi)存模型中的虛擬機(jī)棧原理分析
- Java基礎(chǔ)之創(chuàng)建虛擬機(jī)對(duì)象的過(guò)程詳細(xì)總結(jié)
相關(guān)文章
java8新特性-lambda表達(dá)式入門學(xué)習(xí)心得
這篇文章主要介紹了java8新特性-lambda表達(dá)式入門學(xué)習(xí)心得,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
JAVA構(gòu)造方法/構(gòu)造器以及this使用方式
這篇文章主要介紹了JAVA構(gòu)造方法/構(gòu)造器以及this使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
關(guān)于Java8 parallelStream并發(fā)安全的深入講解
這篇文章主要給大家介紹了關(guān)于Java8 parallelStream并發(fā)安全的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10
Java?使用geotools讀取tiff數(shù)據(jù)的示例代碼
這篇文章主要介紹了Java?通過(guò)geotools讀取tiff,一般對(duì)于tiff數(shù)據(jù)的讀取,都會(huì)借助于gdal,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04
JavaWeb中使用JavaMail實(shí)現(xiàn)發(fā)送郵件功能實(shí)例詳解
這篇文章主要介紹了JavaWeb中使用JavaMail實(shí)現(xiàn)發(fā)送郵件功能的實(shí)例代碼,非常不錯(cuò)具有參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-05-05
SpringBoot詳解如果通過(guò)@Value注解給靜態(tài)變量注入值
這篇文章主要介紹了springboot如何通過(guò)@Value給靜態(tài)變量注入值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
解析阿里GTS開(kāi)源版本fescar分布式事務(wù)
Struts2學(xué)習(xí)筆記(8)-Result常用類型

