jvm class詳解之一中我們介紹了class檔案的結構和如何使用16進制編輯器讀懂class檔案。
今天我們來繼續一起下class檔案中method方法中經過java編譯器編譯後的method位元組碼指令是什麼樣子的
首先我們需要了解jvm有哪些位元組碼指令
是将本地變量中的資料推送入棧中 (什麼是本地變量我們後面聊)
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型資料推送棧頂
load是從本地變量到棧頂,store是從棧頂到本地變量
istore,istore_,lstore,lstore_,fstore,fstore_,dstore,dstor_,astore,astore_
istore:将棧頂int型數值存入制定數組的指定索引位置
除了本地變量到棧頂,還有常量到棧頂
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
getstatic:擷取指定類的靜态域,并将其值壓入棧頂
putstatic:為指定的類的靜态域指派
getfield:擷取指定類的執行個體域,并将其值壓入棧頂
pufield:為指定類的執行個體域指派
invokevirtual:調用執行個體方法
invokespecial:調用超類構造方法,執行個體初始化方法沒有方法
invokestatic:調用靜态方法
invokeinterface:調用接口方法
invokedynamic:調用動态方法
new:建立一個對象
newarray:建立一個指定的原始類型數組
anewarray:建立一個引用型的數組,并将其引用值壓入棧頂
arraylength:擷取數組的長度并壓入棧頂
athrow:将棧頂的異常抛出
checkcast:檢查類型轉換,檢查未通過會抛出classcastexception
instanceof:檢查是否是指定的類的執行個體,如果是,将1壓入棧頂,不是将0壓入棧頂
monitorenter:擷取對象的鎖,用于同步方法或者同步塊
monitorexit:釋放對象的鎖
wide:擴充本地變量的寬度
好至此主要的指令已經介紹完畢,怎麼分類仁者見仁啦。
這個intaddmethod方法傳入兩個int,判斷是否小于0,如果小于傳回-1,都不小于傳回相加值
我們通過javap -verbose helloworldmethod.class 檢視位元組碼指令
我們按照指令一條一條看
0:iload_1:将本地變量中第一個int (a)加載到棧頂
為什麼是a呢,我們再看lovalvariabletable。每個方法都有localvariabletable。是本地變量表。我們可以看到在本地變量表中的第一個int就是a
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
16:iload_3:将本地變量中的第三個int壓入棧頂,取出c
17:ireturn:将棧頂的第一個int傳回
code中 還有另外一個東西
這個是什麼,這個是linenumbertable,其中記錄了編譯出來的位元組碼指令和源碼的對應關系
這個屬性不是很重要。另外就是一個源碼會對應多條指令的
例如源碼中的第5行return -1 ,對應指令為4和5
好了至此我們就知道了我們java檔案編譯後的method中有什麼東西,jvm又是怎樣讀取位元組碼指令做相應操作的了。