JVM004_字节码指令简介
字節碼指令簡介
Java虛擬機指令由操作碼(Opcode)和跟隨其后的零至多個操作數(Operand)組成。
-
操作碼:一個字節長度的,代表某種特定操作含義的數字。
-
操作數:操作碼需要的參數。
字節碼與數據類型
| Tstore | istore | lstore | fstore | dstore | astore | |||
| Tinc | iinc | |||||||
| Taload | baload | saload | iaload | laload | faload | daload | caload | aaload |
| Tastore | bastore | sastore | iastore | lastore | fastore | dastore | castore | astore |
| Tadd | iadd | ladd | fadd | dadd | ||||
| Tsub | isub | lsub | fsub | dsub | ||||
| Tmul | imul | lmul | fmul | dmul | ||||
| Tdiv | idiv | ldiv | fdiv | ddiv | ||||
| Trem | irem | lrem | frem | drem | ||||
| Tneg | ineg | lneg | fneg | dneg | ||||
| Tshl | ishl | lshl | ||||||
| Tshr | ishr | lshr | ||||||
| Tushr | iushr | lushr | ||||||
| Tand | iand | land | ||||||
| Tor | ior | lor | ||||||
| Txor | ixor | lxor | ||||||
| i2T | i2b | i2s | i2l | i2f | i2d | |||
| l2T | l2i | l2f | l2d | |||||
| f2T | f2i | f2l | f2d | |||||
| d2T | d2i | d2l | d2f | |||||
| Tcmp | lcmp | |||||||
| Tcmpl | fcmpl | dcmpl | ||||||
| Tcmpg | fcmpg | dcmpg | ||||||
| if_TempOP | if_iempOP | if_aempOP | ||||||
| Treturn | ireturn | lreturn | freturn | dreturn | areturn |
大部分指令都沒有支持整數類型byte、 char和short, 沒有任何指令支持boolean類型。 編譯器會在編譯期或運行期將byte和short類型的數據帶符號擴展(Sign-Extend) 為相應的int類型數據, 將boolean和char類型數據零位擴展(Zero-Extend) 為相應的int類型數據。 與之類似, 在處理boolean、 byte、 short和char類型的數組時, 也會轉換為使用對應的int類型的字節碼指令來處理。 因此, 大多數對于boolean、 byte、 short和char類型數據的操作, 實際上都是使用相應的對int類型作為運算類型(Computational Type) 來進行的。
加載和存儲指令
加載和存儲指令用于將數據在棧幀中的局部變量表和操作數棧之間來回傳輸 。
-
將一個局部變量加載到操作棧:iload,iload_0,iload_1,iload_2,iload_3,lload,lload_0,lload_1,lload_2,lload_3,fload_0,fload_1,fload_2,fload_3,dload,dload_0,dload_1,dload_2,dload_3,aload,aload_0,aload_1,aload_2,aload_3
iload_0 表示將第0個變量槽中的int型變量推到操作數棧棧頂。
iload_1 表示將第1個變量槽中的int型變量推到操作數棧棧頂。
iload 后面會跟一個參數n,表示將第n個變量槽中的int行變量推送到操作數棧棧頂。
其余的指令同理。
-
將一個數值從操作數棧存儲到局部變量表中:
istore、 istore_<n>、 lstore、 lstore_<n>、 fstore、fstore_<n>、 dstore、 dstore_<n>、 astore、 astore_<n> -
將一個常量加載到操作數棧:
bipush、 sipush、 ldc、 ldc_w、 ldc2_w、 aconst_null、 iconst_m1、iconst_<i>、 lconst_<l>、 fconst_<f>、 dconst_<d> -
擴充局部變量表的訪問索引的指令
wide #拓展本地變量的寬度,用于修改其他指令行為?
public class Test2{public int inc(int a,int b,int c,int d,int e,int f,int h,int j,int k){int z = a + 9;return z;} }利用javac -g Test2.java編譯該源文件,再利用javap -v Test2.class反編譯:
public int inc(int, int, int, int, int, int, int, int, int);descriptor: (IIIIIIIII)Iflags: ACC_PUBLICCode:stack=2, locals=11, args_size=100: iload_1 # 將第1個變量槽中的int型變量推送到棧頂,即將變量a推送到棧頂1: bipush 9 # 將int型常量9推送到棧頂3: iadd # 將棧頂的兩個int型數值相加并壓入棧頂,即a+94: istore 10 # 將棧頂int型數值存入指定的本地變量槽,及將a+9的結果賦值給變量Z6: iload 10 # 將第10個變量槽中的變量推送值棧頂8: ireturn # 彈棧,從當前方法返回int,及return z;LineNumberTable:line 3: 0line 4: 6LocalVariableTable:Start Length Slot Name Signature0 9 0 this LTest2;0 9 1 a I0 9 2 b I0 9 3 c I0 9 4 d I0 9 5 e I0 9 6 f I0 9 7 h I0 9 8 j I0 9 9 k I6 3 10 z I
運算指令
運算指令用于將操作數棧上的兩個值進行某種特定的運算,并將結果重新壓入操作棧頂。可分為對整型數據進行運算的指令和對浮點型數據進行運算指令。
不存在直接支持byte、 short、 char和boolean類型的算術指令 ,而是使用操作int類型的指令代替。
#加法指令: iadd、 ladd、 fadd、 dadd #減法指令: isub、 lsub、 fsub、 dsub #乘法指令: imul、 lmul、 fmul、 dmul #除法指令: idiv、 ldiv、 fdiv、 ddiv #求余指令: irem、 lrem、 frem、 drem #取反指令: ineg、 lneg、 fneg、 dneg #位移指令: ishl、 ishr、 iushr、 lshl、 lshr、 lushr #按位或指令: ior、 lor #按位與指令: iand、 land #按位異或指令: ixor、 lxor #局部變量自增指令: iinc #比較指令: dcmpg、 dcmpl、 fcmpg、 fcmpl、 lcmp類型轉換指令
類型轉換指令可以將兩種不同的數值類型相互轉換 。
**寬化類型轉換:**小范圍類型向大范圍類型的安全轉換。
以下數值類型的寬化類型轉換無序顯示的轉換指令:
1. int到long,float,double2. long到float,double3. float到double窄化類型轉換:窄化類型轉換必須顯示的使用轉換指令來完成
i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l、d2f窄化轉換可能會導致轉換結果產生不同的正負號、不同的數量級的情況,很可能會出現精度丟失。
JVM將浮點值轉換為int或者long時,需要遵循以下規則:
- 如果浮點值是NaN, 那轉換結果就是int或long類型的0。
- 如果浮點值不是無窮大的話, 浮點值使用IEEE 754的向零舍入模式取整, 獲得整數值v。 如果v在目標類型T(int或long) 的表示范圍之類, 那轉換結果就是v; 否則, 將根據v的符號, 轉換為T所能表示的最大或者最小正數。
細節參看:IEEE 754
對象創建與訪問指令
·創建類實例的指令: new ·創建數組的指令: newarray、 anewarray、 multianewarray ·訪問類字段(static字段, 或者稱為類變量) 和實例字段(非static字段, 或者稱為實例變量)的指令: getfield、 putfield、 getstatic、 putstatic ·把一個數組元素加載到操作數棧的指令: baload、 caload、 saload、 iaload、 laload、 faload、daload、 aaload ·將一個操作數棧的值儲存到數組元素中的指令: bastore、 castore、 sastore、 iastore、 fastore、dastore、 aastore ·取數組長度的指令: arraylength ·檢查類實例類型的指令: instanceof、 checkcast操作數棧管理指令
·將操作數棧的棧頂一個或兩個元素出棧: pop、 pop2 ·復制棧頂一個或兩個數值并將復制值或雙份的復制值重新壓入棧頂: dup、 dup2、 dup_x1、dup2_x1、 dup_x2、 dup2_x2 ·將棧最頂端的兩個數值互換: swap控制轉移指令
控制轉移指令可以讓Java虛擬機有條件或無條件地從指定位置指令(而不是控制轉移指令) 的下一條指令繼續執行程序, 從概念模型上理解, 可以認為控制指令就是在有條件或無條件地修改PC寄存器的值。
·條件分支: ifeq、 iflt、 ifle、 ifne、 ifgt、 ifge、 ifnull、 ifnonnull、 if_icmpeq、 if_icmpne、 if_icmplt、if_icmpgt、 if_icmple、 if_icmpge、 if_acmpeq和if_acmpne ·復合條件分支: tableswitch、 lookupswitch ·無條件分支: goto、 goto_w、 jsr、 jsr_w、 ret方法調用和返回指令
·invokevirtual指令: 用于調用對象的實例方法, 根據對象的實際類型進行分派(虛方法分派) ,這也是Java語言中最常見的方法分派方式。 ·invokeinterface指令: 用于調用接口方法, 它會在運行時搜索一個實現了這個接口方法的對象, 找出適合的方法進行調用。 ·invokespecial指令: 用于調用一些需要特殊處理的實例方法, 包括實例初始化方法、 私有方法和父類方法。 ·invokestatic指令: 用于調用類靜態方法(static方法) 。 ·invokedynamic指令: 用于在運行時動態解析出調用點限定符所引用的方法。 并執行該方法。 前面四條調用指令的分派邏輯都固化在Java虛擬機內部, 用戶無法改變, 而invokedynamic指令的分派邏輯是由用戶所設定的引導方法決定的異常處理指令
athrow處理異常是由異常表來完成的,而非字節碼指令。
同步指令
Java虛擬機可以支持方法級的同步和方法內部一段指令序列的同步, 這兩種同步結構都是使用管程(Monitor, 更常見的是直接將它稱為“鎖”) 來實現的。
方法級的同步是隱式的, 無須通過字節碼指令來控制, 它實現在方法調用和返回操作之中。 虛擬機可以從方法常量池中的方法表結構中的ACC_SYNCHRONIZED訪問標志得知一個方法是否被聲明為同步方法。 當方法調用時, 調用指令將會檢查方法的ACC_SYNCHRONIZED訪問標志是否被設置, 如果設置了, 執行線程就要求先成功持有管程, 然后才能執行方法, 最后當方法完成(無論是正常完成還是非正常完成) 時釋放管程。 在方法執行期間, 執行線程持有了管程, 其他任何線程都無法再獲取到同一個管程。 如果一個同步方法執行期間拋出了異常, 并且在方法內部無法處理此異常, 那這個同步方法所持有的管程將在異常拋到同步方法邊界之外時自動釋放。同步一段指令集序列通常是由Java語言中的synchronized語句塊來表示的, Java虛擬機的指令集中monitorenter和monitorexit兩條指令來支持synchronized關鍵字的語義, 正確實現synchronized關鍵字需要Javac編譯器與Java虛擬機兩者共同協作支持 .
下篇文章針對這點做實例分析。
總結
以上是生活随笔為你收集整理的JVM004_字节码指令简介的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot————Web应用
- 下一篇: 多线程相关知识