JVMClass詳解之二Method位元組碼指令

nothingismao發表於2016-03-01

JVM Class詳解之一中我們介紹了Class檔案的結構和如何使用16進位制編輯器讀懂class檔案。
今天我們來繼續一起下Class檔案中Method方法中經過java編譯器編譯後的Method位元組碼指令是什麼樣子的

JVM有哪些位元組碼指令

首先我們需要了解JVM有哪些位元組碼指令

第一類load型別

是將本地變數中的資料推送入棧中 (什麼是本地變數我們後面聊)
iload,iload_,lload,lload_,fload,fload_,dload,dload_,aload,aload_
iload_0:將第一個int型本地變數推送至棧頂
iload中i表示為int型(l為long,f為float,d為double ,a為引用型別),load表示動作為load,
後面的指令大多都是這種結構,先是宣告運算元型別,再說明具體動作。
同理 fload:將本地變數的float型資料推送棧頂

第二類store

load是從本地變數到棧頂,store是從棧頂到本地變數
istore,istore_,lstore,lstore_,fstore,fstore_,dstore,dstor_,astore,astore_
istore:將棧頂int型數值存入制定陣列的指定索引位置

第三類push,const

除了本地變數到棧頂,還有常量到棧頂
bipush,sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_ml,iconst_,lconst_,fconst_,dconst_
ldc:將int,float或String型常量從常量池中推送至棧頂
iconst_0:將int型0推送至棧頂

第四類 算數操作

加:iadd,ladd,fadd,dadd  :將棧頂兩個數值相加並將將結果壓入棧頂
減:is ,ls ,fs ,ds 
乘:imul,lmul,fmul,dmul 
除:idiv,ldiv,fdiv,ddiv 
餘數:irem,lrem,frem,drem 
取負:ineg,lneg,fneg,dneg 
移位:ishl,lshr,iushr,lshl,lshr,lushr 
按位或:ior,lor 
按位與:iand,land 
按位異或:ixor,lxor 
型別轉換:i2l,i2f,i2d,l2f,l2d,f2d(放寬數值轉換) 
i2b,i2c,i2s,l2i,f2i,f2l,d2i,d2l,d2f(縮窄數值轉換)
這個很簡單的顧名思義哈哈。

第五類 比較操作

lcmp:比較棧頂兩個long型數值的大小,並將結果(1,0,-1)壓入棧頂
fcmpl:比較棧頂兩個float型數值的大小,並將結果(1,0,-1)壓入棧頂,當其中一個為NaN,將-1壓入棧頂
fcmpg:。。。。。其中一個為NaN,將1壓入棧頂
dcmpl
dcmpg

第六類 跳轉

ifeq:當棧頂int型數值等於0時跳轉
ifne:當棧頂int型數值不等於0時跳轉
iflt:當棧頂int型數值小於0時跳轉
ifge:大於等於0
ifgt:大於0
ifle:小於等於0
if_icmpeq:比較棧頂兩個int大小,等於0跳轉
if_icmpne:不等於0跳轉
。。。
goto:無條件跳轉
goto,goto_w,jsr,jsr_w,ret 
ifnull:為null時跳轉
ifnonnull:不為null時跳轉
finally關鍵字的實現使用:jsr,jsr_w,ret

第七類 返回操作

ireturn:從當前方法返回int
lreturn:從當前方法放回long
。。。
return:從當前方法返回void

第八類 Class的相關操作

getstatic:獲取指定類的靜態域,並將其值壓入棧頂
putstatic:為指定的類的靜態域賦值
getfield:獲取指定類的例項域,並將其值壓入棧頂
pufield:為指定類的例項域賦值

invokevirtual:呼叫例項方法
invokespecial:呼叫超類構造方法,例項初始化方法沒有方法
invokestatic:呼叫靜態方法
invokeinterface:呼叫介面方法
invokedynamic:呼叫動態方法

new:建立一個物件
newarray:建立一個指定的原始型別陣列
anewarray:建立一個引用型的陣列,並將其引用值壓入棧頂
arraylength:獲取陣列的長度並壓入棧頂
athrow:將棧頂的異常丟擲
checkcast:檢查型別轉換,檢查未通過會丟擲ClassCastException
instanceof:檢查是否是指定的類的例項,如果是,將1壓入棧頂,不是將0壓入棧頂

monitorenter:獲取物件的鎖,用於同步方法或者同步塊
monitorexit:釋放物件的鎖

wide:擴充套件本地變數的寬度

好至此主要的指令已經介紹完畢,怎麼分類仁者見仁啦。

HelloWorld搞起

public class HelloWorldMethod{

public intaddNumber(int a,int b){
if(a<0){
return -1;
}
if( b < 0 ){
return -1;
}
int c = a + b;
returnc;
}
}

這個intAddMethod方法傳入兩個int,判斷是否小於0,如果小於返回-1,都不小於返回相加值

我們通過javap -verbose HelloWorldMethod.class 檢視位元組碼指令
screenshot
screenshot
我們按照指令一條一條看
0:iload_1:將本地變數中第一個int (a)載入到棧頂
為什麼是a呢,我們再看LovalVariableTable。每個方法都有LocalVariableTable。是本地變數表。我們可以看到在本地變數表中的第一個int就是a
screenshot
1:ifge:判斷棧頂的int是否大於0如果大於將1壓入棧頂,如果不大於將0壓入棧頂
分支1:如果當前值不大於0將0壓入棧頂,
分支2:如果當前值大於0跳轉到指令6
4:iconst_m1:將整型-1壓入棧頂 
5:ireturn

6:iload_2:將本地變數彙總第二個int(b)壓入棧頂
7:ifge:判斷棧頂的int是否大於0
分支3:如果當前值不大於0,將棧頂壓入0
分支4:如果當前值大於0,將1壓入棧頂,並跳轉到執行12執行
10:iconst_m1:將int -1壓入棧頂
11:ireturn:返回棧頂int值

12:iload_1:將本地變數第一個int壓入棧頂(a)
13:iload_2:將本地變數第二個int壓入棧頂(b)
14:iadd:將棧頂的兩個int相加並將結果壓入棧頂 a+b
15:istore_3:將棧頂的int值,存入本地變數表中第三個int,第三個int為c,將結果付給了c
screenshot
16:iload_3:將本地變數中的第三個int壓入棧頂,取出c
17:ireturn:將棧頂的第一個int返回

LineNumberTable

Code中 還有另外一個東西
screenshot
這個是什麼,這個是LineNumberTable,其中記錄了編譯出來的位元組碼指令和原始碼的對應關係
這個屬性不是很重要。另外就是一個原始碼會對應多條指令的

例如原始碼中的第5行return -1 ,對應指令為4和5
screenshot

好了至此我們就知道了我們JAVA檔案編譯後的Method中有什麼東西,JVM又是怎樣讀取位元組碼指令做相應操作的了。


相關文章