1. 程式人生 > >深入理解java虛擬機器(六)位元組碼指令簡介

深入理解java虛擬機器(六)位元組碼指令簡介

Java虛擬機器指令是由(佔用一個位元組長度、代表某種特定操作含義的數字)操作碼Opcode,以及跟隨在其後的零至多個代表此操作所需引數的稱為運算元 Operands 構成的。由於Java虛擬機器是面向運算元棧而不是暫存器的架構,所以大多數指令都只有操作碼,而沒有運算元。

位元組碼指令集是一種具有鮮明特點、優劣勢都很突出的指令集架構:

由於限定了Java虛擬機器操作碼的長度為1個位元組,指令集的操作碼不能超過256條。
Class檔案格式放棄了編譯後代碼中運算元長度對齊,這就意味者虛擬機器處理那些超過一個位元組資料的時候,不得不在執行的時候從位元組碼中重建出具體資料的結構。
這種操作在一定程度上會降低一些效能,但這樣做的優勢也非常的明顯:
放棄了運算元長度對齊,就意味著可以省略很多填充和間隔符號
用一個位元組來表示操作碼,也是為了獲取短小精悍的程式碼。
這種追求儘可能小資料量,高傳輸效率的設計是由Java語言之初面向網路、智慧家電技術背景決定的。
Java虛擬機器直譯器執行簡單模型如下:
do{
計算PC暫存器的值+1;
根據PC暫存器只是位置,從位元組碼流中取出操作碼;
if(存在運算元) 從位元組碼中取出運算元;
執行操作碼定義的操作;
}while(位元組碼長度>0);

位元組碼與資料型別
在Java虛擬機器指令集中,大多數的指令都包含了其操作所對應的資料型別資訊。例如,iload 指令用於從區域性變量表中載入int 型的資料到運算元棧中。
但是由於虛擬機器操作碼長度只有一個位元組,所以包含了資料型別的操作碼就為指令集的設計帶來了很大的壓力:如果每一種資料型別相關的指令都支援Java虛擬機器所有執行時資料型別的話,那指令集的資料就會超過256個了。因此虛擬機器只提供了有限的指令集來支援所有的資料型別。
如load 操作, 只有iload、lload、fload、dload、aload用來支援int、long、float、double、reference 型別的入棧,而對於boolean 、byte、short 和char 則沒有專門的指令來進行運算。編譯器會在編譯期或執行期將byte 和 short 型別的資料帶符號擴充套件為int型別的資料,將boolean 和 char 型別的資料零位擴充套件為相應的int 型別資料。與之類似,在處理boolean、byte、short 和 char 型別的陣列時,也會發生轉換。因此,大多數對於boolean、byte、short 和char 型別資料的擦操作,實際上都是使用相應的int 型別作為運算型別。

載入和儲存指令
載入和儲存指令用於將資料從棧幀的區域性變量表和運算元棧之間來回傳輸。
        1)將一個區域性變數載入到運算元棧的指令包括:iload,iload_<n>,lload、lload_<n>、float、 fload_<n>、dload、dload_<n>,aload、aload_<n>。
        2)將一個數值從運算元棧儲存到區域性變量表的指令:istore,istore_<n>,lstore,lstore_<n>,fstore,fstore_<n>,dstore,dstore_<n>,astore,astore_<n>
        3)將常量載入到運算元棧的指令:bipush,sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_ml,iconst_<i>,lconst_<l>,fconst_<f>,dconst_<d>
        4)區域性變量表的訪問索引指令:wide
一部分以尖括號結尾的指令代表了一組指令,如iload_<i>,代表了iload_0,iload_1等,這幾組指令都是帶有一個運算元的通用指令。
運算指令
算術指令用於對兩個運算元棧上的值進行某種特定運算,並把結果重新存入到操作棧頂。
        1)加法指令:iadd,ladd,fadd,dadd
        2)減法指令:isub,lsub,fsub,dsub
        3)乘法指令:imul,lmul,fmul,dmul
        4)除法指令:idiv,ldiv,fdiv,ddiv
        5)求餘指令:irem,lrem,frem,drem
        6)取反指令:ineg,leng,fneg,dneg
        7)位移指令:ishl,ishr,iushr,lshl,lshr,lushr
        8)按位或指令:ior,lor
        9)按位與指令:iand,land
        10)按位異或指令:ixor,lxor
        11)區域性變數自增指令:iinc
        12)比較指令:dcmpg,dcmpl,fcmpg,fcmpl,lcmp
Java虛擬機器沒有明確規定整型資料溢位的情況,但規定了處理整型資料時,只有除法和求餘指令出現除數為0時會導致虛擬機器丟擲異常。
Java虛擬機器要求在浮點數運算的時候,所有結果否必須舍入到適當的精度,如果有兩種可表示的形式與該值一樣,會優先選擇最低有效位為零的。稱之為最接近數舍入模式。
浮點數向整數轉換的時候,Java虛擬機器使用IEEE 754標準中的向零舍入模式,這種模式舍入的結果會導致數字被截斷,所有小數部分的有效位元組會被丟掉。

型別轉換指令
型別轉換指令將兩種Java虛擬機器數值型別相互轉換,這些操作一般用於實現使用者程式碼的顯式型別轉換操作。
JVM直接就支援寬化型別轉換(小範圍型別向大範圍型別轉換):
        1)int型別到long,float,double型別
        2)long型別到float,double型別
        3)float到double型別
但在處理窄化型別轉換時,必須顯式使用轉換指令來完成,這些指令包括:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和 d2f。
將int 或 long 窄化為整型T的時候,僅僅簡單的把除了低位的N個位元組以外的內容丟棄,N是T的長度。這有可能導致轉換結果與輸入值有不同的正負號。
在將一個浮點值窄化為整數型別T(僅限於 int 和 long 型別),將遵循以下轉換規則:
        1)如果浮點值是NaN , 吶轉換結果就是int 或 long 型別的0
        2)如果浮點值不是無窮大,浮點值使用IEEE 754 的向零舍入模式取整,獲得整數v, 如果v在T表示範圍之內,那就過就是v
        3)否則,根據v的符號, 轉換為T 所能表示的最大或者最小正數

物件建立與訪問指令
雖然類例項和陣列都是物件,Java虛擬機器對類例項和陣列的建立與操作使用了不同的位元組碼指令。
        1)建立例項的指令:new
        2)建立陣列的指令:newarray,anewarray,multianewarray
        3)訪問欄位指令:getfield,putfield,getstatic,putstatic
        4)把陣列元素載入到運算元棧指令:baload,caload,saload,iaload,laload,faload,daload,aaload
        5)將運算元棧的數值儲存到陣列元素中執行:bastore,castore,castore,sastore,iastore,fastore,dastore,aastore
        6)取陣列長度指令:arraylength JVM支援方法級同步和方法內部一段指令序列同步,這兩種都是通過moniter實現的。
        7)檢查例項型別指令:instanceof,checkcast

運算元棧管理指令
如同操作一個普通資料結構中的堆疊那樣,Java 虛擬機器提供了一些用於直接操作運算元棧的指令,包括:
        1)將運算元棧的棧頂一個或兩個元素出棧:pop、pop2
        2)複製棧頂一個或兩個數值並將複製值或雙份的複製值重新壓入棧頂:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2。
        3)將棧最頂端的兩個數值互換:swap

控制轉移指令
讓JVM有條件或無條件從指定指令而不是控制轉移指令的下一條指令繼續執行程式。控制轉移指令包括:
        1)條件分支:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnotnull,if_cmpeq,if_icmpne,if_icmlt,if_icmpgt等
        2)複合條件分支:tableswitch,lookupswitch
        3)無條件分支:goto,goto_w,jsr,jsr_w,ret

JVM中有專門的指令集處理int和reference型別的條件分支比較操作,為了可以無明顯標示一個實體值是否是null,有專門的指令檢測null 值。boolean型別和byte型別,char型別和short型別的條件分支比較操作,都使用int型別的比較指令完成,而 long,float,double條件分支比較操作,由相應型別的比較運算指令,運算指令會返回一個整型值到運算元棧中,隨後再執行int型別的條件比較操作完成整個分支跳轉。各種型別的比較都最終會轉化為int型別的比較操作。

方法呼叫和返回指令
invokevirtual指令:呼叫物件的例項方法,根據物件的實際型別進行分派(虛擬機器分派)。
invokeinterface指令:呼叫介面方法,在執行時搜尋一個實現這個介面方法的物件,找出合適的方法進行呼叫。
invokespecial:呼叫需要特殊處理的例項方法,包括例項初始化方法,私有方法和父類方法
invokestatic:呼叫類方法(static)
方法返回指令是根據返回值的型別區分的,包括ireturn(返回值是boolean,byte,char,short和 int),lreturn,freturn,drturn和areturn,另外一個return供void方法,例項初始化方法,類和介面的類初始化i方法使用。

異常處理指令
在Java程式中顯式丟擲異常的操作(throw語句)都有athrow 指令來實現,除了用throw 語句顯示丟擲異常情況外,Java虛擬機器規範還規定了許多執行時異常會在其他Java虛擬機器指令檢測到異常狀況時自動丟擲。
在Java虛擬機器中,處理異常不是由位元組碼指令來實現的,而是採用異常表來完成的。

同步指令

方法級的同步是隱式的,無需通過位元組碼指令來控制,它實現在方法呼叫和返回操作中。虛擬機器從方法常量池中的方法標結構中的 ACC_SYNCHRONIZED標誌區分是否是同步方法。方法呼叫時,呼叫指令會檢查該標誌是否被設定,若設定,執行執行緒持有moniter,然後執行方法,最後完成方法時釋放moniter。
同步一段指令集序列,通常由synchronized塊標示,JVM指令集中有monitorenter和monitorexit來支援synchronized語義。
結構化鎖定是指方法呼叫期間每一個monitor退出都與前面monitor進入相匹配的情形。JVM通過以下兩條規則來保證結結構化鎖成立(T代表一執行緒,M代表一個monitor):
        1)T在方法執行時持有M的次數必須與T在方法完成時釋放的M次數相等
        2)任何時刻都不會出現T釋放M的次數比T持有M的次數多的情況
---------------------
作者:張小琦
來源:CSDN
原文:https://blog.csdn.net/zq602316498/article/details/38847935