天天看點

Java位元組碼指令簡介

Java虛拟機采用基于棧的架構,其指令由操作碼和操作數組成。

  1. 操作碼:一個位元組長度(0~255),意味着指令集的操作碼個數不能操作256條。
  2. 操作數:一條指令可以有零或者多個操作數,且操作數可以是1個或者多個位元組。編譯後的代碼沒有采用操作數長度對齊方式,當處理超出一個位元組的資料時,不得不在運作時從位元組中重建出具體資料的結構,比如16位無符号整數需使用兩個位元組儲存(假設為byte1和byte2),那麼真實值是

    (byte1 << 8) | byte2。

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

  • 優勢:可以省略很多填充和間隔符号,進而減少資料量,具有更高的傳輸效率;Java起初就是為了面向網絡、智能家具而設計的,故更加注重傳輸效率。
  • 劣勢:運作時從位元組碼裡建構出具體資料結構,需要花費部分CPU時間,進而導緻解釋執行位元組碼會損失部分性能。

指令

大多數指令包含了其操作所對應的資料類型資訊,比如iload,表示從局部變量表中加載int型的資料到操作數棧;而fload表示加載float型資料到操作數棧。由于操作碼長度隻有1Byte,是以Java虛拟機的指令集對于特定操作隻提供有限的類型相關指令,并非為每一種資料類型都有相應的操作指令。必要時,有些指令可用于将不支援的類型轉換為可被支援的類型。

對于byte,short,char,boolean類型,往往沒有單獨的操作碼,通過編譯器在編譯期或者運作期将其擴充。對于byte,short采用帶符号擴充,chart,boolean采用零位擴充,轉換成int型的位元組碼來處理,相應的數組也是采用類似的擴充方式轉換為int類型的位元組碼來處理。

下面分門别類來介紹Java虛拟機指令,都以int類型的資料操作為例。

1、加載和存儲指令

用于将資料在棧幀中的局部變量表和操作數棧之間來回傳輸。

  • 将一個局部變量加載到操作棧:iload,iload_等。
  • 将一個數值從操作數棧存儲到局部變量表:istore,istore_等。
  • 将一個常量加載到操作數棧:iconst_,bipush,sipush等。

其中,以尖括号結尾的指令,實際上代表了一組指令,如iload_代表了iload_0,iload_1等。其中iload_0和iload的語義完全一緻,都是表示第一個int變量進棧,而iload_1則表示第二個int型變量進棧。其他的指令都類似。

2、運算指令

運算或者算術指令用于對兩個操作數棧上的值進行某種特定運算,并把結果重新存入到操作棧頂。

大體上可以分為兩種:對整型資料進行運算的指令和對浮點型進行運算的指令。

無論哪種算術指令,都是使用Java虛拟機的資料類型,因為沒有直接支援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
  • List item

其他運算:

  • 位移:ishl,ishr,iushr,lshl,lshr,lushr
  • 按位或: ior,lor
  • 按位與: iand, land
  • 按位異或: ixor, lxor
  • 局部變量自增:iin
  • 比較:dcmpg,dcmpl,fcmpg,fcmpl,lcmp

處理整型資料時:

隻有除法和求餘指令中出現除數為0時,會導緻虛拟機抛出ArithmeticException異常,其他任何運算都不會抛出運作時異常。

處理浮點型資料時:

  • 必須嚴格遵循IEEE 754規範中所規定的行為和限制。
  • 所有的運算結果都必須舍入到适當的精度,采用向最接近數攝入模式。
  • 将浮點數轉為整型數時,采用向零舍入模式(将所有小數部分的位元組丢掉)。
  • 當一個操作産生溢出時,将會采用有符号的無窮大表示,如果某個操作沒有明确的 數學定義時,将會使用NaN來表示,所有NaN值作為操作數的算術操作,結果都是NaN。
  • 所有浮點型的算術操作都不會使虛拟機抛出運作時異常。
3、類型轉換指令

類型轉換指令可以将兩種不同的資料類型進行互相轉換,一般用于實作使用者代碼中的顯示類型轉換操作,或者用于處理将byte,short,char,boolean類型轉換為int型。

隐式轉換:Java虛拟機直接支援(無需顯示的使用轉換指令)

  • int類型轉long、float、double類型。
  • long類型轉float、double類型。
  • float類型轉double類型。

顯示轉換:将高精度(或高寬度的)資料類型轉換為更低精度或寬度的類型。

将一個浮點數窄化為整數類型(int或者long)時,遵循以下規則。

  • 如果浮點數是NaN,那麼整數就是0。
  • 如果不是無窮大,直接用向零舍入模式,或者整數v,如果v在int或long範圍内,那麼結果就是v。
  • 否則,根據v的符号,轉換為v所能表示的最大或者最小整數。
4、對象建立與通路指令

雖然類執行個體和數組都是對象,但Java虛拟機對類執行個體和數組的建立于操作使用了不同的位元組碼指令。

  • 建立類執行個體的指令:new
  • 建立數組:newarray、anewarray、multianewarray
  • 通路類字段(static字段,類變量)和執行個體字段(非static字段,執行個體變量):getstatic、putstatic、getfield、putfield
  • 數組元素 加載到 操作數棧:xaload (x可為b,c,s,i,l,f,d,a)
  • 操作數棧的值 存儲到數組元素: xastore (x可為b,c,s,i,l,f,d,a)
  • 取數組長度:arraylength
  • 檢查類執行個體類型:instanceof、checkcast
5、操作數棧管理指令
  1. pop用于棧頂數值出棧操作:pop、pop2
  2. dup用于指派棧頂的指定個數的數值,并将其壓入棧頂指定次數:dup、dup2、dup_x1
  3. swap 棧頂的兩個數值互換,且不能是long/double
  • 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次
6、控制轉移指令

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

  • 條件分支:ifeq、iflt、ifnull、ifnonnull等
  • 複合分支:tableswitch、lookupswitch
  • 無條件分支:goto、goto_w、jsr、jsr_w、ret
7、方法調用和傳回指令
Java位元組碼指令簡介
Java位元組碼指令簡介
8、同步與異常

異常:

Java程式顯式抛出異常: athrow指令。在Java虛拟機中,處理異常(catch語句)不是由位元組碼指令來實作,而是采用異常表來完成。

同步:

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

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