深入理解java虛擬機器(5)---位元組碼執行引擎,深入理解位元組碼

來源:互聯網
上載者:User

深入理解java虛擬機器(5)---位元組碼執行引擎,深入理解位元組碼

位元組碼是什麼東西?

以下是百度的解釋:

位元組碼(Byte-code)是一種包含執行程式、由一序列 op 代碼/資料對組成的二進位檔案。位元組碼是一種中間碼,它比機器碼更抽象。

它經常被看作是包含一個執行程式的二進位檔案,更像一個物件模型。位元組碼被這樣叫是因為通常每個 opcode 是一位元組長,

但是指令碼的長度是變化的。每個指令有從 0 到 255(或十六進位的: 00 到FF)的一位元組作業碼,被參數例如寄存器或記憶體位址跟隨。

 

說了這麼多,你可能還是不明白到底是什麼東西。好吧,簡單點,就是java編譯以後的那個東東,“.class”檔案。

所以class檔案就是位元組碼檔案,是由虛擬機器執行的檔案。也就是java語言和C & C++語言的區別就是,整個編譯執行過程多了一個虛擬

機這一步。這個在“深入理解java虛擬機器(3)---類的結構” 一文中已經解釋,這是一個裡程碑式的設計。上一節講了虛擬機器是如何載入

一個class的,這一節就講解虛擬機器是如何執行class檔案的。

 

java虛擬機器規範,規定了虛擬機器位元組碼的執行概念性模型。具體的虛擬機器可以有不同的實現。

運行時棧幀結構

棧是每個線程專屬的記憶體。

棧幀儲存了局部變數表,運算元棧,動態串連,和返回地址等。

每一個方法的執行 對應的一個棧幀在虛擬機器裡面從如棧到出棧的過程。

只有位於棧頂的棧幀才有有效,對應的方法稱為當前方法。

執行引擎啟動並執行所有指令只針對當前棧幀和當前方法。

1.局部變數表

局部變數表存放的一組變數的儲存空間。存放方法參數和方法內部定義的局部變數表。

在java編譯成class的時候,已經確定了局部變數表所需分配的最大容量。

局部變數表的最小單位是一個Slot。

虛擬機器規範沒有明確規定一個Slot佔多少大小。只是規定,它可以放下boolean,byte,...reference &return address.

reference 是指一個對象執行個體的引用。關於reference的大小,目前沒有明確的指定大小。但是我們可以理解為它就是類似C++中的指標。

局部變數表的讀取方式是索引,從0開始。所以局部變數表可以簡單理解為就是一個表.

局部變數表的分配順序如下:

this 引用。可以認為是隱式參數。

方法的參數表。

根據局部變數順序,分配Solt。

一個變數一個solt,64為的佔2個solt。java中明確64位的是long & double

為了儘可能的節約局部變數表,Solt可以重用。

注意:局部變數只給予分配的記憶體,沒有class對象的準備階段,所以局部變數在使用前,必須先賦值。

2.運算元棧

運算元棧在概念上很像寄存器。

java虛擬機器無法使用寄存器,所以就有運算元棧來存放資料。

虛擬機器把運算元棧作為它的工作區——大多數指令都要從這裡彈出資料,執行運算,然後把結果壓回運算元棧。

比如,iadd指令就要從運算元棧中彈出兩個整數,執行加法運算,其結果又壓回到運算元棧中,看看下面的樣本,

它示範了虛擬機器是如何把兩個int類型的局部變數相加,再把結果儲存到第三個局部變數的:

begin

iload_0 // push the int in local variable 0 onto the stack

iload_1 // push the int in local variable 1 onto the stack

iadd // pop two ints, add them, push result

istore_2 // pop int, store into local variable 2

end

運算元棧 的資料讀取、寫入就是出棧和如棧操作。

3.動態串連

每個棧幀都包含一個指向運行時常量池的引用,持有這個引用是為了支援動態串連。

符號池的引用,有一部分是在第一次使用或者初始化的時候就確定下來,這個稱為靜態引用。

還有一部分是在每次執行的時候採取確定,這個就是動態串連。

4.方法返回地址

方法只有2中退出方式,正常情況下,遇到return指令退出。還有就是異常退出。

正常情況:一般情況下,棧幀會儲存 在程式計數器中的調用者的地址。虛擬機器通過這個方式,執行方法調用者的地址,

然後把傳回值壓入調用者中的運算元棧。

異常情況:方法不會返回任何值,返回地址有異常表來確定,棧幀一般不儲存資訊。

5.方法調用

方法調用階段不是執行該方法,而僅僅時確認要調用那個方法。class檔案在編譯階段沒有串連這一過程,、

所以動態串連這個在C++就已經有的技術,在java運用到了一個新的高度。所有的函數(除了私人方法,構造方法 & 靜態方法,下同),理論上

都可以時C++裡面的虛函數。所以所有的函數都需要通過動態綁定來確定“明確”的函數實體。

解析

所有方法調用的目標方法都是常量池中的符號引用。在類的載入解析階段,會將一部分目標方法轉化為直接引用。(可以理解為具體方法的直接地址)

可以轉化的方法,主要為靜態方法 & 私人方法。

Java虛擬機器提供5中方法調用命令:

invokestatic:調用靜態方法

invokespecial:調用構造器,私人方法和父類方法

invokevirtual:調用虛方法

invokeinterface:調用介面方法

invokedynamic:現在運行時動態解析出該方法,然後執行。

invokestatic & invokespecial 對應的方法,都是在載入解析後,可以直接確定的。所以這些方法為非虛方法。

java規定 final修飾的是一種非虛方法。

指派

靜態指派

先看一個例子:

package com.joyfulmath.jvmexample.dispatch;import com.joyfulmath.jvmexample.TraceLog;/** * @author deman.lu * @version on 2016-05-19 13:53 */public class StaticDispatch {    static abstract class Human{    }    static class Man extends Human{    }    static class Woman extends Human{    }    public void sayHello(Human guy)    {        TraceLog.i("Hello guy!");    }    public void sayHello(Man man)    {        TraceLog.i("Hello gentleman!");    }    public void sayHello(Woman man)    {        TraceLog.i("Hello lady!");    }    public static void action()    {        Human man = new Man();        Human woman = new Woman();        StaticDispatch dispatch = new StaticDispatch();        dispatch.sayHello(man);        dispatch.sayHello(woman);    }}
05-19 13:58:05.538 14881-14881/com.joyfulmath.jvmexample I/StaticDispatch: sayHello: Hello guy! [at (StaticDispatch.java:24)]05-19 13:58:05.539 14881-14881/com.joyfulmath.jvmexample I/StaticDispatch: sayHello: Hello guy! [at (StaticDispatch.java:24)]

結果執行了public void sayHello(Human guy)函數。這不是應該多態嗎?

Human man = new Man();

這裡的Human我們理解為靜態類型,後面的Man是實際類型。我們在編譯器只知道靜態類型,後面的實際類型等到動態串連的時候才知道。

所以對於sayHello方法,虛擬機器在重載時,是通過參數的靜態類型,而不是實際類型來判斷使用那個方法的。

如果對類型做強制轉換:

    public static void action()    {        Human man = new Man();        Human woman = new Woman();        StaticDispatch dispatch = new StaticDispatch();        dispatch.sayHello(man);        dispatch.sayHello(woman);        dispatch.sayHello((Man)man);        dispatch.sayHello((Woman)woman);    }05-19 14:08:29.000 21838-21838/com.joyfulmath.jvmexample I/StaticDispatch: sayHello: Hello guy! [at (StaticDispatch.java:24)]05-19 14:08:29.001 21838-21838/com.joyfulmath.jvmexample I/StaticDispatch: sayHello: Hello guy! [at (StaticDispatch.java:24)]05-19 14:08:29.001 21838-21838/com.joyfulmath.jvmexample I/StaticDispatch: sayHello: Hello gentleman! [at (StaticDispatch.java:29)]05-19 14:08:29.002 21838-21838/com.joyfulmath.jvmexample I/StaticDispatch: sayHello: Hello lady! [at (StaticDispatch.java:34)]

如果強轉了以後,類型也跟著變化了。

靜態分配的典型應用是方法重載。但是方法重載有時候不是唯一的,所以只能選合適的。

比如:

    public void sayHello(int data)    {        TraceLog.i("Hello int!");    }    public void sayHello(long  data)    {        TraceLog.i("Hello long");    }

當sayHello(1)的時候,一般情況下會調用int型的方法,但是如果注釋調,只有long型的方法,long型參數方法就會被調用。

 

動態指派

上面講的是重載,這裡是重寫(@Override)

package com.joyfulmath.jvmexample.dispatch;import com.joyfulmath.jvmexample.TraceLog;/** * @author deman.lu * @version on 2016-05-19 14:26 */public class DynamicDispatch {    static abstract class Human{        protected abstract void sayHello();    }    static class Man extends Human{        @Override        protected void sayHello() {            TraceLog.i("Hello gentleman!");        }    }    static class Woman extends Human{        @Override        protected void sayHello() {            TraceLog.i("Hello lady!");        }    }    public static void action()    {        Human man = new Man();        Human woman = new Woman();        man.sayHello();        woman.sayHello();        man = new Woman();        man.sayHello();    }}

先來看上面標紅的這句:方法要解析man 的sayhello,問題是man是什麼東西,我在解析的時候,是不知道的。所以“man.sayHello();”具體執行的那個類的方法,是需要在虛擬機器

動態串連的時候才知道,這個就是多態。如果使用javap分析就可以知道這句話,在class檔案裡面是ynamicDispatch$Human: sayHello. 是的class檔案不知道這個sayhello到底要去

調哪個方法。

invokevirtual指令解析的過程大概如下:首先在運算元棧裡第一個元素的實際類型,即為C。

如果在類型C中找到與常量描述符相同的類名和方法,則許可權校正通過後,即為找到該法方法,則返回這個方法的直接引用。

否則,對C的父類進行依次尋找。

這個過程通俗一點就是,先從當前類裡面尋找“同名”的該方法,如果沒有,就從C的父類裡面找,知道找到為止!

這個找到的方法,就是我們實際要調的方法。

如果找不到,就是exception。一般情況下,編譯工具會幫我們避免這種情況。

單指派和多指派

概念上理解比較麻煩,說白了一點就是重載和重寫都存在的情況:

package com.joyfulmath.jvmexample.dispatch;import com.joyfulmath.jvmexample.TraceLog;/** * @author deman.lu * @version on 2016-05-19 15:02 */public class MultiDispatch {    static class QQ{}    static class _360{}    public static class Father{        public void hardChoice(QQ qq){            TraceLog.i("Father QQ");        }        public void hardChoice(_360 aa){            TraceLog.i("Father 360");        }    }    public static class Son extends Father{        public void hardChoice(QQ qq){            TraceLog.i("Son QQ");        }        public void hardChoice(_360 aa){            TraceLog.i("Son 360");        }    }    public static void action()    {        Father father = new Father();        Father son = new Son();        father.hardChoice(new _360());        son.hardChoice(new QQ());    }}
05-19 15:07:44.429 29011-29011/com.joyfulmath.jvmexample I/MultiDispatch$Father: hardChoice: Father 360 [at (MultiDispatch.java:19)]05-19 15:07:44.429 29011-29011/com.joyfulmath.jvmexample I/MultiDispatch$Son: hardChoice: Son QQ [at (MultiDispatch.java:25)]

結果沒有任何懸念,但是過程還是需要明確的。hardChoice的選擇是在靜態編譯的時候就確認的。

而son.hardchoise 已經確認了函數的類型,只是需要進一步確認實體類型。所以動態串連是單指派。

 

動態語言支援:

使用C++語言可以定義一個調用方法:

void sort(int list[],const int size,int (*compare)(int,int));

但是java很難做到這一點,

void sort(List list,Compare c);Compare 一般要用介面實現。

在java 1.7 有一種方法可以支援該功能 MethodHandle。

這部分內容,由於我本地環境無法配置還調用,將會再後續更新。

 

鋪墊了這麼多,下面來講講位元組碼的執行

6.基於棧的位元組碼執行引擎

基於棧的指令集 和基於寄存器的指令集。

先看一個加法過程:

iconst_1

iconst_1

iadd

istore_0

這是基於棧的,也就是上文說的運算元棧。

先把2個元素要入棧,然後相加,放回棧頂,然後把棧頂的值存在slot 0裡面。

基於寄存器的就不解釋了。

基於寄存器 和基於棧的指令集現在都存在。所以很難說孰優孰劣。

基於棧的指令集 是和硬體無關的,而基於寄存器則依賴於硬體基礎。基於寄存器在效率上優勢。

但是虛擬機器的出現,就是為了提供跨平台的支援,所以jvm的執行引擎是基於棧的指令集。

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

以下是javap的分析結果:

 

以片描述了整個執行過程中代碼,運算元棧,& 局部變數表的變化。

 

 

這些過程只是一個概念性模型,實際虛擬機器會有很多最佳化的情況。

聲明:本文相關圖片來之參考書面,相關著作權歸原作者所有。

參考:

《深入理解java虛擬機器》 周志明

 

  

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.