Java虛擬機指令由一個位元組長度的、代表某種特定意義的操作碼(Opcode)以及其後的零個至多個代表此操作參數的操作數構成。虛擬機器中許多指令並不包含操作數,只有一個操作碼。若忽略異常,JVM解釋器使用一下為程式碼即可有效運作。
複製代碼代碼如下:
do{
自動計算PC暫存器以及從PC暫存器的位置取出操作碼
if(存在操作數) 取出操作數;
執行操作碼所定義的操作;
}while(處理下一次迴圈)
操作數的數量以及長度,取決於操作碼,若一個操作數長度超過了一個字節,將會以Big-Endian順序儲存(高位在前字節碼),其值應為(byte1<<8) |byte2。
字節碼指令流是單字節對齊,只有"tableswitch"和"lookupswitch"兩個指令例外,它們的操作數比較特殊,以4字節為界限劃分的,需要預留出相應的空位來實現對齊。
限制Java虛擬機器操作碼的長度為一個字節,且放棄編譯後程式碼的參數長度對齊,是為了獲得短小精簡的編譯程式碼,即使可能會讓JVM實現付出一定效能成本為代價。由於操作碼只能有一個位元組長度,故限制了指令集的數量,又沒有假設資料是對齊好的,意味著資料超過一個位元組時,必須從位元組重建出具體的資料結構,會損失一些性能。
資料類型與Java虛擬機
在JVM中的指令集中,大多數指令包含了其操作對應的資料類型資訊。如iload指令從局部變數表載入int型的資料到操作數堆疊中,而fload載入的是float類型的資料。
對於大部分與資料類型相關的字節碼指令,他們的操作碼助記符都有特殊的字元來表示:i代表int類型,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。有一些單獨指令可以在必要的時候用來將一些不支援的類型轉換為可被支援的類型。
載入和儲存指令
載入和儲存指令用於將資料從堆疊幀的局部變數表和操作數棧之間來回傳輸。
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時會導致虛擬機器拋出異常。
載入和儲存指令
載入和儲存指令用於將資料從哦你哦過棧幀的局部變數表和操作數棧之間來回傳輸。
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虛擬機器數值類型相互轉換,這些操作一般用於實作使用者程式碼的明確類型轉換操作。
JVM支援寬化型別轉換(小範圍型別轉換成大範圍型別):
1)int型別到long,float,double型別
2)long型別到float,double型
3)float到double類型
窄花類型轉換指令:i2b,i2c,i2s,l2i,f2i,f2l,d2l和d2f,狹窄類型轉換可能會導致轉換結果產生不同的正負號,不同數量級,轉換過程可能會導致數值遺失精確度。如int或long型別轉換整數型別T時,轉換過程是僅丟棄最低位N個位元組意外的內容(N是型別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
7)檢查實例類型指令:instanceof,checkcast
操作數堆疊管理指令
直接操作操作數棧的指令:pop,pop2,dup,dup2,dup_x1,dup2_x1,dup_x2,dup2_x2和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 方法使用。
同步
JVM支援方法級同步和方法內部一段指令序列同步,這兩種都是透過moniter實現的。
方法級的同步是隱式的,無需透過字節碼指令來控制,它實現在方法呼叫和返回操作中。虛擬機器從方法常數池中的方法標結構中的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的次數多的情況