方法調(diào)用
方法調(diào)用并不等同于方法執(zhí)行,方法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用方法的版本(即調(diào)用的哪一個方法),暫時還不涉及方法內(nèi)部具體的運(yùn)行過程
.在程序運(yùn)行時,進(jìn)行方法調(diào)用是最普遍的,最頻繁的操作.但是Class文件的編譯過程中不包含傳統(tǒng)編譯中的連接步驟,一切方法調(diào)用在Class文件里面存儲的只是符號引用,而不是方法在實際運(yùn)行時內(nèi)存布局中的入口地址(相當(dāng)于之前說的直接引用).這個特性給Java帶來了更加強(qiáng)大的動態(tài)擴(kuò)展能力,但也使得Java方法調(diào)用過程變得相對復(fù)雜起來,需要在類加載期間,甚至到運(yùn)行期間才能確定目標(biāo)方法的直接引用.
解析
所有方法調(diào)用中的目標(biāo)方法在Class文件里面都是一個常量池的符號引用,在類加載的解析階段,會將其中的一部分符號引用轉(zhuǎn)化為直接引用
,這中捷信能成立的前提是:方法在程序真正運(yùn)行之前就有一個可確定的調(diào)用版本,并且這個方法的調(diào)用版本在運(yùn)行期是不可改變的.換句話說,調(diào)用目標(biāo)在程序代碼寫好,編譯器進(jìn)行編譯時就必須確定下來.這類方法的調(diào)用成為解析(Resolution).
在Java語言中符合“編譯期可知運(yùn)行期不可變”這個要求的的方法主要包括靜態(tài)方法和私有方法兩大類
這兩種方法不能被通過繼承或者是背的方式被重寫,因此他們適合在類的加載階段進(jìn)行解析
- 靜態(tài)方法:與類型直接關(guān)系
- 私有方法:在外部不可訪問
與之相對應(yīng)的是,在Java虛擬機(jī)里面提供了5條方法調(diào)用字節(jié)碼指令
- invokestatic:調(diào)用靜態(tài)方法。
- invokespecial:調(diào)用實例構(gòu)造器<init>方法静袖、私有方法和父類方法征峦。
- invokevirtual:調(diào)用所有的虛方法爪飘。
- invokeinterface:調(diào)用接口方法,會在運(yùn)行時再確定一個實現(xiàn)此接口的對象昌腰。
- invokedynamic:先在運(yùn)行時動態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法您宪,然后再執(zhí)行該方法,在此之前的4條調(diào)用指令芍躏,分派邏輯是固化在Java虛擬機(jī)內(nèi)部的邪乍,而invokedynamic指令的分派邏輯是由用戶所設(shè)定的引導(dǎo)方法決定的。
只要能被invokestatic和invokespecial指令調(diào)用的方法,都可以在解析階段確定為宜的調(diào)用版本,符合這個條件的有靜態(tài)方法,私有方法,實例構(gòu)造方法,父類方法4類方法,他們在類加載的時候就會把符號引用解析為該方法的直接引用.這些方法可以稱為非虛方法
,與其相反,其他方法稱為虛方法
(出去final方法).
Java中的非虛方法除了使用invokestatic,invokespecial調(diào)用方法之外還有一種,就是被final修飾的方法.雖然final方法是使用invokevirtual指令調(diào)用
,但是由于它無法被覆蓋,沒有其他版本,所以也無須對方法接收方進(jìn)行多態(tài)選擇,又或者說多態(tài)選擇的結(jié)果肯定是唯一的.在Java語言規(guī)范當(dāng)中明確說明了final方法是一種非虛方法
.
解析調(diào)用一定是一個靜態(tài)過程,在編譯期就完全確定,在類裝載的解析階段就會把涉及的符號引用全部轉(zhuǎn)換為可用的直接引用,不會延遲到運(yùn)行期再去完成
.而分派(Dispath)調(diào)用則可能是靜態(tài)的也可能是動態(tài)的,根據(jù)分派依據(jù)的宗量數(shù)可分為單分派和多分派对竣。這兩類分派方式的兩兩組合就構(gòu)成了靜態(tài)單分派庇楞、靜態(tài)多分派、動態(tài)單分派否纬、動態(tài)多分派4種分派組合情況.
分派
Java的三大基本特性,繼承,封裝,多態(tài).
1.靜態(tài)分派
方法靜態(tài)分派演示
public class StaticDispatch {
static abstract class Human{
}
static class Woman extends Human{
}
static class Man extends Human{
}
public void sayHello(Human gay){
System.out.println("Hello guy!");
}
public void sayHello(Woman gay){
System.out.println("Hello lady!");
}
public void sayHello(Man gay){
System.out.println("Hello gentleman!");
}
public static void main(String[] args) {
Human man = new Man();
Human woman=new Woman();
StaticDispatch sr=new StaticDispatch();
sr.sayHello(man);
sr.sayHello(woman);
}
}
運(yùn)行結(jié)果
Hello guy!
Hello guy!
先按照代碼定義兩個重要的概念;
Human man = new Human();
把上述代碼中的"Human
"稱為變量的靜態(tài)類型(Static Type),或者叫做外觀類型(Apparent Type)吕晌,后面的“Man”則稱為變量的實際類型(Actual Type),靜態(tài)類型和實際類型在程序中都會發(fā)生一些變化,區(qū)別是靜態(tài)類型的變化僅僅在使用時發(fā)生變化,變量本身的靜態(tài)類型是不會被改變的
.并且最終的靜態(tài)類型是在編譯期可知的
;而實際類型變化的結(jié)果是在運(yùn)行期才可以確定
,編譯器在編譯程序的時候并不知道一個對象的實際類型是什么.
//實際類型變化
Human man=new Man();
man=new Woman()临燃;
//靜態(tài)類型變化
sr.sayHello((Man)man)
sr.sayHello((Woman)man)
main()里面的兩次sayHello()方法調(diào)用睛驳,在方法接收者已經(jīng)確定是對象“sr”的前提下壁拉,使用哪個重載版本,就完全取決于傳入?yún)?shù)的數(shù)量和數(shù)據(jù)類型柏靶。
代碼中刻意地定義了兩個靜態(tài)變量類型相同但是實際類型不同變量,但虛擬機(jī)(準(zhǔn)確說是編譯器)在重載時是通過參數(shù)的靜態(tài)類型而不是實際類型作為判定依據(jù).并且靜態(tài)類型實在編譯期可知的.因此,在編譯階段,javac編譯器會根據(jù)參數(shù)的靜態(tài)類型決定使用哪個重載版本,所以選擇了sayHello(Human)作為調(diào)用目標(biāo),并把這個方法的符號引用寫到main()方法里的兩條invokevirtual指令的參數(shù)中.
所有依賴靜態(tài)類型來定位方法執(zhí)行版本的分派動作稱為靜態(tài)分派.靜態(tài)分配的典型應(yīng)用是方法重載.靜態(tài)分派發(fā)生在編譯階段
,因此確定靜態(tài)分派的動作實際上不是由虛擬機(jī)來執(zhí)行的.另外,編譯器雖然能確定出方法的重載版本,但在很多的情況下這個重載版本并不是"唯一的",往往只能確定一個"更加適合的版本"
.這種模糊的結(jié)論在由0和1構(gòu)成的計算機(jī)世界中算是比"罕見"的事情,產(chǎn)生這種模糊結(jié)論的主要原因是字面量不需要定義,所以字面量沒有顯式的靜態(tài)類型
,它的靜態(tài)類型型只能通過語言上的規(guī)則去理解和推斷.
public class Overload {
public static void sayHello(Object arg){
System.out.println("Hello Object");
}
public static void sayHello(long arg){
System.out.println("Hello long");
}
public static void sayHello(int arg){
System.out.println("Hello int");
}
public static void sayHello(Character arg){
System.out.println("Hello Character");
}
public static void sayHello(char... arg){
System.out.println("Hello char...");
}
public static void sayHello(char arg){
System.out.println("Hello char");
}
public static void sayHello(Serializable arg){
System.out.println("Hello Serializable");
}
public static void main(String[] args) {
sayHello('a');
}
}
運(yùn)行結(jié)果
Hello char
因為'a'是一個char類型的數(shù)據(jù),自然會尋找參數(shù)類型為char的重載方法,如果注釋掉sayHello(char arg)方法,那輸出就會變?yōu)?/p>
Hello int
這時發(fā)生了一次自動類型轉(zhuǎn)換,'a'除了可以代表一個字符串溃论,還可以代表數(shù)字97(字符'a'的Unicode數(shù)值為十進(jìn)制數(shù)字97)屎蜓,因此參數(shù)類型為int的重載也是合適的。我們繼續(xù)注釋掉sayHello(int arg)方法钥勋,那輸出會變?yōu)椋?/p>
Hello long
發(fā)生了兩次自動類型轉(zhuǎn)換炬转,'a'轉(zhuǎn)型為整數(shù)97之后,進(jìn)一步轉(zhuǎn)型為長整數(shù)97L算灸,匹配了參數(shù)類型為long的重載扼劈。實
際上自動轉(zhuǎn)型還能繼續(xù)發(fā)生多次按照char->int->long->float->double的順序轉(zhuǎn)型進(jìn)行匹配。但不會匹配到byte和short類型的重載菲驴,因為char到byte或short的轉(zhuǎn)型是不安全的荐吵。繼續(xù)注釋掉sayHello(long arg),那么輸出就會變?yōu)?
Hello Character
這時發(fā)生了一次自動裝箱
,'a'被包裝為它的封裝類型java.lang.Character赊瞬,所以匹配到了參數(shù)類型為Character的重載
繼續(xù)注釋掉sayHello(Character arg)方法先煎,那輸出會變?yōu)椋?/p>
Hello Serializable
出現(xiàn)Hello Serializable,是因為java.lang.Serializable是java.lang.Character類實現(xiàn)的一個接口巧涧,當(dāng)自動裝箱之后發(fā)現(xiàn)還是找不到裝箱類薯蝎,但是找到了裝箱類實現(xiàn)了的接口類型
,所以緊接著又發(fā)生一次自動轉(zhuǎn)型谤绳。char可以轉(zhuǎn)型成int占锯,但是Character是絕對不會轉(zhuǎn)型為Integer的,它只能安全地轉(zhuǎn)型為它實現(xiàn)的接口或父類缩筛。Character還實現(xiàn)了另外一個接java.lang.Comparable<Character>消略,如果同時出現(xiàn)兩個參數(shù)分別為Serializable和Comparable<Character>的重載方法,那它們在此時的優(yōu)先級是一樣的
歪脏。編譯器無法確定要自動轉(zhuǎn)型為哪種類型疑俭,會提示類型模糊,拒絕編譯
婿失。程序必須在調(diào)用時顯式地指定字面量的靜態(tài)類型钞艇,如:sayHello((Comparable<Character>)'a'),才能編譯通過豪硅。
下面繼續(xù)注釋掉sayHello(Serializable arg)方法哩照,輸出會變?yōu)?
Hello object
這時是char裝箱后轉(zhuǎn)型為父類了,如果有多個父類,那將在繼承關(guān)系中從下往上開始搜索,越接近上層的優(yōu)先級的越低
.即使調(diào)用傳入的參數(shù)值為null時,這個規(guī)則仍然適用.把sayHello(Object arg)也注釋掉,輸出將會變?yōu)椋?/p>
Hello char...
可見變長參數(shù)的重載優(yōu)先級是最低的懒浮,這時候字符'a'被當(dāng)做了一個數(shù)組元素飘弧。
靜態(tài)方法會在類加載期就進(jìn)行解析识藤,而靜態(tài)方法顯然也是可以擁有重載版本的,選擇重載版本的過程也是通過靜態(tài)分派完成的
2.動態(tài)分配
public class DynamicDispatch {
static abstract class Human{
protected abstract void sayHello();
}
static class Woman extends Human {
@Override
protected void sayHello() {
System.out.println("Woman say hello");
}
}
static class Man extends Human {
@Override
protected void sayHello() {
System.out.println("Man say hello");
}
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();
}
}
運(yùn)行結(jié)果
Man say hello
Woman say hello
Woman say hello
顯然這里不可能根據(jù)靜態(tài)類型來決定,因為靜態(tài)類型同樣都是HUman的兩個變量,man和woman兩個變量在調(diào)用sayHello()方法時執(zhí)行了不同的行為,并且變量man在兩次調(diào)用中執(zhí)行了不同的方法.這是因為這兩個變量的實際類型不同
0~15行的字節(jié)碼是準(zhǔn)備動作次伶,作用是建立man和woman的內(nèi)存空間痴昧、調(diào)用Man和Woman類型的實例構(gòu)造器,將這兩個實例的引用存放在第1冠王、2個局部變量表Slot之中赶撰,這個動作也就對應(yīng)了代碼中的這兩句:
Human man = new Man();
Human woman = new Woman();
接下來的16~21句是關(guān)鍵部分,16柱彻、20兩句分別把剛剛創(chuàng)建的兩個對象的引用壓到棧頂豪娜,這兩個對象是將要執(zhí)行的sayHello()方法的所有者,稱為接收者(Receiver)哟楷;17和21句是方法調(diào)用指令瘤载,這兩條調(diào)用指令單從字節(jié)碼角度來看,無論是指令(都是invokevirtual)還是參數(shù)(都是常量池中第22項的常量卖擅,注釋顯示了這個常量是Human.sayHello()的符號引用)完全一樣的鸣奔,但是這兩句指令最終執(zhí)行的目標(biāo)方法并不相同。原因就需要從invokevirtual指令的多態(tài)查找過程開始說起惩阶,invokevirtual指令的運(yùn)行時解析過程大致分為以下幾個步驟:
- 1)找到操作數(shù)棧頂?shù)牡谝粋€元素所指向的對象的實際類型溃蔫,記作C。
- 2)如果在類型C中找到與常量中的描述符和簡單名稱都相符的方法琳猫,則進(jìn)行訪問權(quán)限校驗伟叛,如果通過則返回這個方法的直接引用,查找過程結(jié)束脐嫂;如果不通過统刮,則返回java.lang.IllegalAccessError異常。
- 3)否則账千,按照繼承關(guān)系從下往上依次對C的各個父類進(jìn)行第2步的搜索和驗證過程侥蒙。
- 4)如果始終沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常匀奏。
由于invokevirtual指令執(zhí)行的第一步就是在運(yùn)行期確定接收者的實際類型鞭衩,所以兩次調(diào)用中的invokevirtual指令把常量池中的類方法符號引用解析到了不同的直接引用上,這個過程就是Java語言中方法重寫的本質(zhì)娃善。我們把這種在運(yùn)行期根據(jù)實際類型確定方法執(zhí)行版本的分派過程稱為動態(tài)分派论衍。
3.單分派和多分派
方法的宗量:方法接收者與方法的參數(shù)的統(tǒng)稱.
根據(jù)分派基于多少種宗量,可以將分派劃分為單分派和多分派兩種聚磺。
- 單分派:根據(jù)一個宗量對目標(biāo)方法進(jìn)行選擇.
- 多分派:根據(jù)多余一個宗量對目標(biāo)進(jìn)行選擇
public class Dispatch {
static class QQ{}
static class _360{}
public static class Father{
public void hardChoice(QQ arg){
System.out.println("Father choice QQ");
}
public void hardChoice(_360 arg){
System.out.println("Father choice 360");
}
}
public static class Son extends Father{
@Override
public void hardChoice(QQ arg){
System.out.println("Son choice QQ");
}
@Override
public void hardChoice(_360 arg){
System.out.println("Son choice 360");
}
}
public static void main(String[] args) {
Father father = new Father();
Father son = new Son();
father.hardChoice(new _360());
son.hardChoice(new QQ());
}
}
運(yùn)行結(jié)果
Father choice 360
Son choice QQ
編譯階段編譯器的選擇過程坯台,也就是靜態(tài)分派的過程。這時選擇目標(biāo)方法的依據(jù)有兩點(diǎn):一是靜態(tài)類型是Father還是Son瘫寝,二是方法參數(shù)是QQ還是360蜒蕾。這次選擇結(jié)果的最終產(chǎn)物是產(chǎn)生了兩條invokevirtual指令稠炬,兩條指令的參數(shù)分別為常量池中指向Father.hardChoice(360)及Father.hardChoice(QQ)方法的符號引用。因為是根據(jù)兩個宗量進(jìn)行選擇咪啡,所以Java語言的靜態(tài)分派屬于多分派類型首启。
運(yùn)行階段虛擬機(jī)的選擇,也就是動態(tài)分派的過程撤摸。在執(zhí)行“son.hardChoice(newQQ())”這句代碼時闽坡,更準(zhǔn)確地說,是在執(zhí)行這句代碼所對應(yīng)的invokevirtual指令時愁溜,由于編譯期已經(jīng)決定目標(biāo)方法的簽名必須為hardChoice(QQ),虛擬機(jī)此時不會關(guān)心傳遞過來的參數(shù)“QQ”到底是“騰訊QQ”還是“奇瑞QQ”外厂,因為這時參數(shù)的靜態(tài)類型冕象、實際類型都對方法的選擇不會構(gòu)成任何影響,唯一可以影響虛擬機(jī)選擇的因素只有此方法的接受者的實際類型是Father還是Son汁蝶。因為只有一個宗量作為選擇依據(jù)渐扮,所以Java語言的動態(tài)分派屬于單分派類型。
4.虛擬機(jī)動態(tài)分派的實現(xiàn)
動態(tài)分派是非常頻繁的動作,而且動態(tài)分配的方法版本選擇過程需要在類方法數(shù)據(jù)中搜索合適的目標(biāo)方法
掖棉,因此在虛擬機(jī)的實際實現(xiàn)中基于性能的考慮墓律,大部分實現(xiàn)都不會真正地進(jìn)行如此頻繁的搜索。
面對這種情況幔亥,最常用的“穩(wěn)定優(yōu)化”手段就是為類在方法區(qū)中建立一個虛方法表(Vritual Method Table耻讽,也稱為vtable,與此對應(yīng)的帕棉,在invokeinterface執(zhí)行時也會用到接口方法表——Inteface Method Table针肥,簡稱itable),使用虛方法表索引來代替元數(shù)據(jù)查找以提高性能香伴。
虛方法表中存放著各個方法的實際入口地址慰枕。如果某個方法在子類中沒有被重寫,那子類的虛方法表里面的地址入口和父類相同方法的地址入口是一致的即纲,都指向父類的實現(xiàn)入口具帮。如果子類中重寫了這個方法,子類方法表中的地址將會替換為指向子類實現(xiàn)版本的入口地址低斋。
為了程序?qū)崿F(xiàn)上的方便蜂厅,具有相同簽名的方法,在父類膊畴、子類的虛方法表中都應(yīng)當(dāng)具有一樣的索引序號葛峻,這樣當(dāng)類型變換時,僅需要變更查找的方法表巴比,就可以從不同的虛方法表中按索引轉(zhuǎn)換出所需的入口地址术奖。
方法表一般在類加載的連接階段進(jìn)行初始化
,準(zhǔn)備了類的變量初始化值后,虛擬機(jī)會把該類的方法表也初始化完畢.
動態(tài)語言支持
MethodHandle與與Reflection
從本質(zhì)上講礁遵,Reflection和MethodHandle機(jī)制都是在模擬方法調(diào)用,但Reflection是在模擬Java代碼層次的方法調(diào)用采记,而MethodHandle是在模擬字節(jié)碼層次的方法調(diào)用佣耐。
Reflection中的java.lang.reflect.Method
對象遠(yuǎn)比MethodHandle機(jī)制中的java.lang.invoke.MethodHandle
對象所包含的信息多。前者是方法在Java一端的全面映像唧龄,包含了方法的簽名兼砖、描述符以及方法屬性表中各種屬性的Java端表示方式,還包含執(zhí)行權(quán)限等的運(yùn)行期信息既棺。而后者僅僅包含與執(zhí)行該方法相關(guān)的信息讽挟。用通俗的話來講,Reflection是重量級丸冕,而MethodHandle是輕量級耽梅。
Reflection API的設(shè)計目標(biāo)是只為Java語言服務(wù)的,而MethodHandle則設(shè)計成可服務(wù)于所有Java虛擬機(jī)之上的語言胖烛,其中也包括Java語言
基于棧的字節(jié)碼解釋執(zhí)行引擎
解釋執(zhí)行
現(xiàn)代經(jīng)典編譯原理的思路:在執(zhí)行前先對程序源碼進(jìn)行詞法分析和語法分析處理眼姐,把源碼轉(zhuǎn)化為抽象語法樹(Abstract Syntax Tree,AST)。對于一門具體語言的實現(xiàn)來說佩番,詞法分析众旗、語法分析以至后面的優(yōu)化器和目標(biāo)代碼生成器都可以選擇獨(dú)立于執(zhí)行引擎,形成一個完整意義的編譯器去實現(xiàn)趟畏,這類代表是C/C++語言贡歧。也可以選擇把其中一部分步驟(如生成抽象語法樹之前的步驟)實現(xiàn)為一個半獨(dú)立的編譯器,這類代表是Java語言赋秀。又或者把這些步驟和執(zhí)行引擎全部集中封裝在一個封閉的黑匣子之中艘款,如大多數(shù)的JavaScript執(zhí)行器。
Java語言中沃琅,Javac編譯器完成了程序代碼經(jīng)過詞法分析哗咆、語法分析到抽象語法樹,再遍歷語法樹生成線性的字節(jié)碼指令流的過程益眉。因為這一部分動作是在Java虛擬機(jī)之外進(jìn)行的晌柬,而解釋器在虛擬機(jī)的內(nèi)部,所以Java程序的編譯就是半獨(dú)立的實現(xiàn)郭脂。
基于棧的指令集與基于寄存器的指令集
Java編譯器輸出的指令流年碘,基本上[1]是一種基于棧的指令集架構(gòu)(Instruction SetArchitecture,ISA),指令流中的指令大部分都是零地址指令展鸡,它們依賴操作數(shù)棧進(jìn)行工作屿衅。與之相對的另外一套常用的指令集架構(gòu)是基于寄存器的指令集,最典型的就是x86的二地址指令集莹弊,說得通俗一些涤久,就是現(xiàn)在我們主流PC機(jī)中直接支持的指令集架構(gòu)涡尘,這些指令依賴寄存器進(jìn)行工作。
示例:分別使用這兩種指令集計算“1+1”的結(jié)果
- 基于棧的指令集
iconst_1
iconst_1
iadd
istore_0
兩條iconst_1指令連續(xù)把兩個常量1壓入棧后响迂,iadd指令把棧頂?shù)膬蓚€值出棧考抄、相加,然后把結(jié)果放回棧頂蔗彤,最后istore_0把棧頂?shù)闹捣诺骄植孔兞勘淼牡?個Slot中川梅。
- 基于寄存器的指令集
mov eax,1
add eax然遏,1
mov指令把EAX寄存器的值設(shè)為1贫途,然后add指令再把這個值加1,結(jié)果就保存在EAX寄存器里面待侵。
指令集 | 優(yōu)點(diǎn) | 缺點(diǎn) |
---|---|---|
基于棧的指令集 | 可移植,由硬件直接供給 | 受到硬件的約束 |
棧架構(gòu)指令集 | 代碼相對更加緊湊,編譯器實現(xiàn)更加簡單 | 執(zhí)行速度相對來說會稍慢一些 |
棧實現(xiàn)在內(nèi)存之中丢早,頻繁的棧訪問也就意味著頻繁的內(nèi)存訪問,相對于處理器來說诫给,內(nèi)存始終是執(zhí)行速度的瓶頸
孝治。盡管虛擬機(jī)可以采取棧頂緩存的手段炮温,把最常用的操作映射到寄存器中避免直接內(nèi)存訪問瘫想,但這也只能是優(yōu)化措施而不是解決本質(zhì)問題的方法乃沙。由于指令數(shù)量和內(nèi)存訪問的原因闸迷,所以導(dǎo)致了棧架構(gòu)指令集的執(zhí)行速度會相對較慢.
基于棧解釋器執(zhí)行過程
代碼示例:
public int calc(){
int a=100餐弱;
int b=200窟赏;
int c=300僧家;
return(a+b)*c瞄摊;
}
使用javap命令進(jìn)行查看字節(jié)碼指令
public int calc()勋又;
Code:
Stack=2,Locals=4换帜,Args_size=1
0:bipush 100
2:istore_1
3:sipush 200
6:istore_2
7:sipush 300
10:istore_3
11:iload_1
12:iload_2
13:iadd
14:iload_3
15:imul
16:ireturn
}
javap提示這段代碼需要深度為2的操作數(shù)棧和4個Slot的局部變量空間
上面的執(zhí)行過程僅僅是一種概念模型楔壤,虛擬機(jī)最終會對執(zhí)行過程做一些優(yōu)化來提高性能,實際的運(yùn)作過程不一定完全符合概念模型的描述……更準(zhǔn)確地說惯驼,實際情況會和上面描述的概念模型差距非常大蹲嚣,這種差距產(chǎn)生的原因是虛擬機(jī)中解析器和即時編譯器都會對輸入的字節(jié)碼進(jìn)行優(yōu)化.