JVM(5)—字節碼執行引擎

JVM中的執行引擎在執行java代碼的時候,通常有解釋執行(經過解釋器執行)和編譯執行(經過即時編譯器產生本地代碼執行)兩種選擇。java

棧幀程序員

  • 定義:

棧幀是用於支持快看小說網虛擬機進行方法調用和方法執行的數據結構,它位於虛擬機棧裏面。數據結構

  • 做用:

每一個他的偏執欲方法從調用開始到執行完成的過程當中,都對應着一個棧幀在虛擬機棧裏面從入棧到出棧的過程。ide

 

 

特色:
(1)棧幀包括了局部變量表,操做數棧等,究竟是須要多大的局部變量表,多深的操做數棧是在編譯期肯定的。由於一個棧幀須要分配多少內存,不會受到程序運行期變量數據的影響。優化

(2)兩個棧幀之間的數據共享。在概念模型中,兩個棧幀是徹底獨立的,可是在虛擬機的實現裏會作一些優化處理,令兩個棧幀出現一部分重疊。這樣在進行方法調用時,就能夠共用一部分數據,無須進行額外的參數複製傳遞。spa


(1)局部變量表

局部變量表是一組江亞顧飛遠變量值存儲空間,用於存放方法參數和方法內部定義的局部變量。3d

//方法參數   
max(int a,int b)

  

int a;//全局變量
void say(){
   int b=0;//局部變量
 }

  

局部變量和類變量(用static修飾的變量)不一樣
類變量有兩次賦初始值的過程:準備階段(賦予系統初始值)和初始化階段(賦予程序員定義的初始值)。因此即便在初始化階段沒有爲類變量賦值也不要緊,它仍然有一個肯定的初始值。
但局部變量不同,若是定義了,但沒有賦初始值,是不能使用的。對象

(2)操做棧

當一個方法剛剛神級保鏢開始執行的時候,這個方法的操做數棧是空的,在方法的執行過程當中,會有各類字節碼指令往操做數棧中寫入和提取內容,也就是出棧、入棧操做。
在這裏插入圖片描述blog

例如,計算:圖片

int a=2+3

  

操做數棧中最接近棧頂的兩個元素是2和3,當執行iadd指令時,會將2和3出棧並相加,而後將相加的結果5入棧。

(3)動態連接

Class文件的常量池中存有大量的符號引用願歲月可回首,字節碼中的方法調用指令就以常量池中指向方法的符號引用做爲參數。這些符號引用分爲兩部分:

靜態解析:在類加載階段或第一次使用的時候就轉化爲直接引用。
動態連接:在每一次運行期間轉化爲直接引用。

(4)返回地址

當一個方法開始執行後,一往情深,傅少愛妻如命只有兩種方式能夠退出這個方法:正常退出、異常退出。不管採用何種退出方式,在方法退出以後,都須要返回到方法被調用的位置,程序才能繼續執行。

當方法正常退出時
調用者的PC計數器做爲返回地址。棧幀中通常會保存這個計數器值。

當方法異常退出時
返回地址是顧小秋霍言城要經過異常處理器表來肯定的。棧幀中通常不會保存這部分信息。

方法調用

 

方法調用是肯定調用哪個方法。

 

(1)解析

 

對「編譯器可知,運行期不可變」的方法進行調用稱爲解析。符合這種要求的方法主要包括

 

  • 靜態方法,用static修飾的方法
  • 私有方法,用private修飾的方法

(2)分派

分派講解了虛擬機如何肯定正確的目標方法。分派分爲靜態分派和動態分派。講解靜動態分派以前,咱們重生80:肥妻喜臨門先看個多態的例子。

 

Human man=new Man();

  

在這段代碼中,Human爲靜態類型,其在編譯期是可知的。Man是實際類型,結果在運行期纔可肯定,編譯期在編譯程序的時候並不知道一個對象的實際類型是什麼。

  • 靜態分派:

全部依賴靜態類型來定位方法執行版本的分派動做稱爲靜態分派。它的典型應用是重載。

public class StaticDispatch{  
   static abstract class Human{  
    }  
   static class Man extends Human{
    }
    static class Woman extends Human{
    }
    public void say(Human hum){  
        System.out.println("I am human");  
    }  
    public void say(Man hum){  
        System.out.println("I am man");  
    }  
    public void say(Woman hum){  
        System.out.println("I am woman");  
    }  
    
    public static void main(String[] args){  
        Human man = new Man();  
        Human woman = new Woman();  
        StaticDispatch sr = new StaticDispatch();  
        sr.say(man);  
        sr.say(woman);  
    }  
} 

  運行結果是:

I am human
I am human

  

總裁霸愛萌妻乖乖淪陷爲何會產生這個結果呢?
由於編譯器在重載時,是經過參數的靜態類型而不是實際類型做爲判斷依據的。在編譯階段,javac編譯器會根據參數的靜態類型決定使用哪一個重載版本,因此兩個對say()方法的調用實際爲sr.say(Human)。

  • 動態分派:

在運行期根據實際類型肯定方法執行版本的分派過程。它的典型應用是重寫。

 

public class DynamicDispatch{  
   static abstract class Human{  
            protected abstract void say();
    }  
   static class Man extends Human{
            @Override
             protected abstract void say(){
             System.out.println("I am man");  
            }
    }
    static class Woman extends Human{
         @Override
             protected abstract void say(){
             System.out.println("I am woman ");  
            }
    }
    
    public static void main(String[] args){  
        Human man = new Man();  
        Human woman = new Woman();  
        man.say();
        woman.say();
        man=new Woman();
        man.say();
    }  
} 

  

I am man
I am woman 
I am woman 

  

這彷佛纔是咱們平時敲的java代碼。對於方法重寫,頭號追妻令:老婆,休想逃在運行時才肯定調用哪一個方法。因爲Human的實際類型是man,所以調用的是man的name方法。其他的同理。
動態分派的實現依賴於方法區中的虛方法表,它裏面存放着各個方法的實際入口地址。若是某個方法在子類中被重寫了,那子類方法表中的地址將會替換爲指向子類實現版本的入口地址,不然,指向父類的實現入口。

單分派和多分派:
方法的接收者與方法的參數統稱爲方法的宗量,根據分派基於多少種宗量,分爲單分派和多分派。
在靜態分派中,須要調用者的實際類型和方法參數的類型才能肯定方法版本,因此其是多分派類型。在動態分派中,已經知道了參數的實際類型,因此此時只需知道方法調用者的實際類型就能夠肯定出方法版本,因此其是單分派類型。綜上,java是一門靜態多分派,動態單分派的語言。

字節碼解釋執行引擎

虛擬機中的字節碼解釋執行引擎是基於棧的。下面經過一段代碼來仔細看一下其解釋的執行過程

 

public int calc(){  
    int a = 100;  
    int b = 200;  
    int c = 300;  
    return (a + b) * c;  
} 

 

  

第一步:將100入棧。

 

第二步:將操做棧中的100出棧並存放到局部變量中。後面的200,300同理。

第三步:將局部變量表中的100複製到操做數棧頂。葉星辰厲景墨

第四步:將局部變量表中的200複製到操做數棧頂。

第五步:將100和200出棧,作整型加法,最後將結果300從新入棧。

第六步:將第三個數300從局部變量表複製到棧頂。接下來就是將兩個300出棧,進行整型乘法,將蘇允最後的結果90000入棧。

第七步:方法結束,將操做數棧頂的整型值返回給此方法的調用者。

相關文章
相關標籤/搜索