JVM指令主要包含了一下幾種類型:加載和存儲指令、運算指令成玫、類型轉換指令加酵、對象創(chuàng)建與訪問指令、操作數(shù)棧管理指令哭当、控制轉移指令猪腕、方法調用和返回指令、異常處理指令钦勘、同步指令等陋葡。
基于棧的解釋器執(zhí)行過程
下面看一下一個簡單的代碼片段,如下所示:
public class StackTest {
public int calc() {
int a = 100;
int b = 200;
int c = 300;
return (a + b) * c;
}
}
通過jclasslib工具或者javap -verbose命令彻采,可以得到calc()方法的字節(jié)碼指令腐缤。如下所示:
0 bipush 100
2 istore_1
3 sipush 200
6 istore_2
7 sipush 300
10 istore_3
11 iload_1
12 iload_2
13 iadd
14 iload_3
15 imul
16 ireturn
下面來具體的說明一下整個方法的執(zhí)行過程:
上面的指令執(zhí)行過程只是一個概念模型捌归,JVM會對過程做一些優(yōu)化來提高性能,JVM在實際運行時可能執(zhí)行過程差距比較大岭粤,并且不同虛擬機的執(zhí)行也不盡相同惜索。
加載和存儲指令
加載和存儲指令用于數(shù)據(jù)在棧幀中的局部變量表和操作數(shù)棧之間的來回傳輸。
將一個局部變量加載到操作數(shù)棧:iload剃浇、iload_巾兆、lload、lload_虎囚、fload角塑、fload_、dload溜宽、dload吉拳、aload、aload适揉。
將一個數(shù)值從操作數(shù)棧存儲到局部變量表:istore留攒、istore_、lstore嫉嘀、lstore_炼邀、fstore、fstore_剪侮、dstore拭宁、dstore_、astore瓣俯、astore_杰标。
將一個常量加載到操作數(shù)棧:bipush、sipush彩匕、ldc腔剂、ldc_w、ldc2_w驼仪、aconst_null掸犬、iconst_ml、iconst_绪爸、lconst_湾碎、fconst_、dconst_奠货。
擴充局部變量表的訪問索引的指令:wide介褥。
運算指令
運算指令作用于操作數(shù)棧上面的2個值的特定運算,并且把結果重新存入操作數(shù)棧頂。大體上可以分為2類:對整型呻顽、浮點型數(shù)值運算雹顺。因為JVM指令集中沒有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
位移指令:ishl订晌、ishr虏辫、iushr、lshl锈拨、lshr乒裆、lushr
按位或指令:ior、lor
按位與指令:iand推励、land
按位異或指令:ixor、lxor
局部變量自增指令:iinc
比較指令:dcmpg肉迫、dcmpl验辞、fcmpg、fcmpl喊衫、lcmp
類型轉換指令
類型轉換指令可以將2種不同類型的數(shù)值相互轉換跌造,這些轉換一般實現(xiàn)于代碼中的顯示類型轉換,主要有以下類型:
int類型到long、float或者double類型
long類型到float壳贪、double類型
float類型到double類型
對于顯示的類型轉換陵珍,一般情況下都是窄化類型轉換(也就是丟失精度的轉化,如:long轉為int等)违施。常見的轉換指令有:i2b互纯、i2c、i2s磕蒲、l2i留潦、f2i、f2l辣往、d2i兔院、d2l、d2f等站削。
對象創(chuàng)建與訪問指令
對于普通對象和數(shù)組的創(chuàng)建坊萝,JVM分別使用了不同的指令去處理。
創(chuàng)建普通對象的指令:new
創(chuàng)建數(shù)組的指令:newarray许起、anewarray、multianewarray
訪問類變量(static類型)和實例變量(非static類型)的指令:getstatic街氢、putstatic扯键、getfield珊肃、putfield
把一個數(shù)組加載到操作數(shù)棧的指令:baload、caload伦乔、saload、iaload烈和、laload、faload招刹、daload恬试、aaload
將一個操作數(shù)棧的值存儲到數(shù)組元素中的指令:bastore疯暑、castore、sastore妇拯、iastore洗鸵、fastore、dastore仗嗦、aastore
取數(shù)組長度的指令:arraylength
檢查普通對象類型的指令:instanceof膘滨、checkcast
操作數(shù)棧管理指令
如同一個普通的堆棧一樣,JVM提供了直接操作操作數(shù)棧的指令稀拐。
將操作數(shù)棧頂?shù)?個或2個元素出棧:pop1火邓、pop2
復制棧頂1個或2個元素,并將副本的1份或者2份重新入棧:dup钩蚊、dup2贡翘、dup_x1、dup2_x1砰逻、dup_x2鸣驱、dup2_x2
將棧頂?shù)膬蓚€數(shù)值互換:swap
控制轉移指令
控制轉移指令可以讓JVM,跳轉到指定的偏移地址的字節(jié)碼執(zhí)行蝠咆。從上面的模型圖看來踊东,就是修改程序計數(shù)器的值。
分支條件:ifeq刚操、iflt闸翅、ifle、ifne菊霜、ifgt坚冀、ifge、ifnull鉴逞、ifnonnull记某、if_icmpeq、if_icmpne构捡、if_icmplt液南、if_icmpgt、if_icmple勾徽、if_icmpge滑凉、if_acmpeq、if_acmpne喘帚。
復合條件分支:tableswitch畅姊、lookupswitch
無條件分支:goto、goto_w吹由、jsr涡匀、jsr_w、ret溉知。
方法調用和返回指令
方法調用包含了以下指令。
invokevirtual指令:用于調用對象的實例方法,根據(jù)對象的實際類型分派(虛方法分派)级乍。
invokeinterface指令:用于調用接口方法舌劳,它會在運行時搜索一個實現(xiàn)這個接口的對象,找出合適的方法調用玫荣。
invokespecial指令:用于調用一些需要特殊處理的實例方法甚淡,包括初始化方法、私有方法捅厂、父類方法贯卦。
invokestatic指令:用于調用類方法(static方法)。
invokedynamic指令:用于在運行時動態(tài)解析出調用點限定符所引用的方法焙贷,并執(zhí)行。
上述的前4條指令都是固化在JVM內部的辙芍,invokedynamic的分派邏輯是由用戶所設定的引導方法決定的故硅。
方法的調用指令與數(shù)據(jù)類型無關,而方法的返回指令是根據(jù)返回值區(qū)分的往踢。包括:ireturn(當返回值是boolean徘层、byte、char山上、short英支、int)、lreturn干花、freturn、dreturn和areturn抡驼。return指令提供給:返回值為void的指令肿仑、實例方法初始化碎税、接口類方法初始化馏锡。
異常處理指令
Java程序中顯示拋出異常的操作都是由athrow指令實現(xiàn)的杯道。
同步指令
JVM可以支持方法級的同步和方法內的同步,這兩種同步結構都是由管程(Monitor)來實現(xiàn)的萎庭。
方法級的同步是隱式的齿拂,無需通過字節(jié)碼指令來控制。JVM可以從方法常量池的方法表結構中的ACC_SYNCHRONIZED訪問標志达舒,得到其是否為同步方法叹侄。
對于方法中的同步塊,JVM中使用monitorenter和monitorexit兩條指令來支持贯底。下面參見一個代碼清單:
public class SyncInstruction {
void onlyMe(Object f) {
synchronized (f) {
System.out.println("synchronized control.");
}
}
}
對應的指令序列如下:
0 aload_1 // 將對象f入棧
1 dup // 復制棧頂元素(即f的引用)
2 astore_2 // 將棧頂元素存儲到局變量表Slot 2中
3 monitorenter // 以棧頂元素(f)作為鎖撒强,開始同步
4 getstatic #2 <java/lang/System.out> // 訪問System的靜態(tài)屬性out
7 ldc #3 <synchronized control.> // 將字符串常量"synchronized control."壓入操作數(shù)棧頂
9 invokevirtual #4 <java/io/PrintStream.println> // 調用PrintStream.println()方法
12 aload_2 // 將局部變量表Slot 2的元素(f)入棧
13 monitorexit // 退出同步
14 goto 22 (+8) // 方法正常退出飘哨,跳轉到22行
17 astore_3 // 這里開始是異常路徑,它的偏移量記錄在異常表中浊服,如下圖所示
18 aload_2 // 將局部變量表Slot 2的元素(f)入棧
19 monitorexit // 退出同步
20 aload_3 // 將局變量表Slot 3的元素(異常對象)入棧
21 athrow // 把異常重新拋出給onlyMe()方法的調用者
22 return // 方法正常返回
異常表如下所示: