JVM Class詳解之二 Method字節碼指令

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又是怎樣讀取字節碼指令做相應操作的了。

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