Java字節碼詳解(三)字節碼指令(轉)

一、概述

Java虛擬機採用基於棧的架構,其指令由操作碼和操作數組成。

  • 操作碼:一個字節長度(0~255),意味着指令集的操作碼個數不能操作256條。
  • 操作數:一條指令可以有零或者多個操作數,且操作數可以是1個或者多個字節。編譯後的代碼沒有采用操作數長度對齊方式,比如16位無符號整數需使用兩個字節儲存(假設爲byte1和byte2),那麼真實值是 (byte1 << 8) | byte2。

放棄操作數對齊操作數對齊方案:

  • 優勢:可以省略很多填充和間隔符號,從而減少數據量,具有更高的傳輸效率;Java起初就是爲了面向網絡、智能傢俱而設計的,故更加註重傳輸效率。
    劣勢:運行時從字節碼裏構建出具體數據結構,需要花費部分CPU時間,從而導致解釋執行字節碼會損失部分性能。

二、指令

大多數指令包含了其操作所對應的數據類型信息,比如iload,表示從局部變量表中加載int型的數據到操作數棧;而fload表示加載float型數據到操作數棧。由於操作碼長度只有1Byte,因此Java虛擬機的指令集對於特定操作只提供有限的類型相關指令,並非爲每一種數據類型都有相應的操作指令。必要時,有些指令可用於將不支持的類型轉換爲可被支持的類型。

對於byte,short,char,boolean類型,往往沒有單獨的操作碼,通過編譯器在編譯期或者運行期將其擴展。對於byte,short採用帶符號擴展,chart,boolean採用零位擴展。相應的數組也是採用類似的擴展方式轉換爲int類型的字節碼來處理。 下面分門別類來介紹Java虛擬機指令,都以int類型的數據操作爲例。

2.1 棧操作相關

load和store

  • load 命令:用於將局部變量表的指定位置的相應類型變量加載到棧頂;
  • store命令:用於將棧頂的相應類型數據保入局部變量表的指定位置;
變量進棧 含義 變量保存 含義
iload 第1個int型變量進棧 istore 棧頂nt數值存入第1局部變量
iload_0 第1個int型變量進棧 istore_0 棧頂int數值存入第1局部變量
iload_1 第2個int型變量進棧 istore_1 棧頂int數值存入第2局部變量
iload_2 第3個int型變量進棧 istore_2 棧頂int數值存入第3局部變量
iload_3 第4個int型變量進棧 istore_3 棧頂int數值存入第4局部變量
lload 第1個long型變量進棧 lstore 棧頂long數值存入第1局部變量
fload 第1個float型變量進棧 fstore 棧頂float數值存入第1局部變量
dload 第1個double型變量進棧 dstore 棧頂double數值存入第1局部變量
aload 第1個ref型變量進棧 astore 棧頂ref對象存入第1局部變量

const、push和ldc

  • const、push:將相應類型的常量放入棧頂
  • ldc:則是從常量池中將常量
常量進棧 含義
aconst_null null進棧
iconst_m1 int型常量-1進棧
iconst_0 int型常量0進棧
iconst_1 int型常量1進棧
iconst_2 int型常量2進棧
iconst_3 int型常量3進棧
iconst_4 int型常量4進棧
iconst_5 int型常量5進棧
lconst_0 long型常量0進棧
fconst_0 float型常量0進棧
dconst_0 double型常量0進棧
bipush byte型常量進棧
sipush short型常量進棧
常量池操作 含義
ldc int、float或String型常量從常量池推送至棧頂
ldc_w int、float或String型常量從常量池推送至棧頂(寬索引)
ldc2_w long或double型常量從常量池推送至棧頂(寬索引)

pop和dup

  • pop用於棧頂數值出棧操作;
  • dup用於賦值棧頂的指定個數的數值,並將其壓入棧頂指定次數;
棧頂操作 含義
pop 棧頂數值出棧(不能是long/double)
pop2 棧頂數值出棧(long/double型1個,其他2個)
dup 複製棧頂數值,並壓入棧頂
dup_x1 複製棧頂數值,並壓入棧頂2次
dup_x2 複製棧頂數值,並壓入棧頂3次
dup2 複製棧頂2個數值,並壓入棧頂
dup2_x1 複製棧頂2個數值,並壓入棧頂2次
dup2_x2 複製棧頂2個數值,並壓入棧頂3次

swap 棧頂的兩個數值互換,且不能是long/double

注意:dup2對於long、double類型的數據就是一個,對於其他類型的數據,纔是真正的兩個,這個的2代表的是2個slot的數據。

2.2 對象相關

字段調用

字段調用 含義
getstatic 獲取類的靜態字段,將其值壓入棧頂
putstatic 給類的靜態字段賦值
getfield 獲取對象的字段,將其值壓入棧頂
putfield 給對象的字段賦值

方法調用

方法調用 作用 解釋
invokevirtual 調用實例方法 虛方法分派
invokestatic 調用類方法 static方法
invokeinterface 調用接口方法 運行時搜索合適方法調用
invokespecial 調用特殊實例方法 包括實例初始化方法、父類方法
invokedynamic 由用戶引導方法決定 運行時動態解析出調用點限定符所引用方法

方法返回

方法返回 含義
ireturn 當前方法返回int
lreturn 當前方法返回long
freturn 當前方法返回float
dreturn 當前方法返回double
areturn 當前方法返回ref

對象和數組

  • 創建類實例: new
  • 創建數組:newarray、anewarray、multianewarray
  • 數組元素 加載到 操作數棧:xaload (x可爲b,c,s,i,l,f,d,a)
  • 操作數棧的值 存儲到數組元素: xastore (x可爲b,c,s,i,l,f,d,a)
  • 數組長度:arraylength
  • 類實例類型:instanceof、checkcast

2.3 運算指令

運算指令是用於對操作數棧上的兩個數值進行某種運算,並把結果重新存入到操作棧頂。Java虛擬機只支持整型和浮點型兩類數據的運算指令,所有指令如下:

運算 int long float double
加法 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
  • 自增:iin
  • 比較:dcmpg,dcmpl,fcmpg,fcmpl,lcmp

2.4 類型轉換

類型轉換用於將兩種不同類型的數值進行轉換。

(1) 對於寬化類型轉換(小範圍向大範圍轉換),無需顯式的轉換指令,並且是安全的操作。各種範圍從小到大依次排序: int, long, float, double。

(2)對於窄化類型轉換,必須顯式地調用類型轉換指令,並且該過程很可能導致精度丟失。轉換規則中需要特別注意的是當浮點值爲NaN, 則轉換結果爲int或long的0。雖然窄化運算可能會發生上/下限溢出和精度丟失等情況,但虛擬機規範明確規定窄化轉換U不可能導致虛擬機拋出異常。

類型轉換指令:i2b, i2c,f2i等等。

2.5 流程控制

控制指令是指有條件或無條件地修改PC寄存器的值,從而達到控制流程的目標

  • 條件分支:ifeq、iflt、ifnull、ifnonnull等
  • 複合分支:tableswitch、lookupswitch
  • 無條件分支:goto、goto_w、jsr、jsr_w、ret

2.6 同步與異常

異常

Java程序顯式拋出異常: athrow指令。在Java虛擬機中,處理異常(catch語句)不是由字節碼指令來實現,而是採用異常表來完成。

同步

方法級的同步和方法內部分代碼的同步,都是依靠管程(Monitor)來實現的。

Java語言使用synchronized語句塊,那麼Java虛擬機的指令集中通過monitorenter和monitorexit兩條指令來完成synchronized的功能。爲了保證monitorenter和monitorexit指令一定能成對的調用(不管方法正常結束還是異常結束),編譯器會自動生成一個異常處理器,該異常處理器的主要目的是用於執行monitorexit指令。

2.7 小結

在基於堆棧的的虛擬機中,指令的主戰場便是操作數棧,除了load是從局部變量表加載數據到操作數棧以及store儲存數據到局部變量表,其餘指令基本都是用於操作數棧的。

原文:Jvm系列2-字節碼指令

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章