Java虛拟機的指令由一個位元組長度的、代表着某種特定操作含義的操作碼以及跟随其後的零至多個代表此操作所需參數的操作數所構成。虛拟機中許多指令并不包含操作數,隻有一個操作碼。
Java虛拟機限制操作碼的長度為1個位元組,是以最多隻能有256個指令。
指令格式
以下指令格式,是基于Oracle JDK編譯後,通過javap工具生成的指令描述格式。
<index> <opcode> [<operand1> [<operand2>...]] [<comment>]
<index>
指令操作碼在方法位元組碼指令數組中的索引,也可以認為是相對于方法起始處的位元組偏移量。其中,指令數組指方法對應的Code屬性的code[]數組,該數組用于存放方法的位元組碼指令。
該索引可以作為控制轉移指令的跳轉目标。例如,goto 8指令表示跳轉到索引為8的指令上繼續執行。
<opcode>
指令的操作碼助記符。例如,iconst_0、istore_1、iload_1和return等。
<operandN>
指令操作數,一條指令可以有0至多個操作數。例如,iconst_0沒有操作數,bipush有1個操作數,iinc有2個操作數。
<comment>
指令行尾的注釋。注釋内容通常以//開始。
每一行中,表示運作時常量池索引的操作數前,會有一個井号。在指令後的注釋中,會帶有對這個操作數的描述,例如:
: invokespecial #8 // Method java/lang/Object."<init>":()V
: ldc2_w #19 // double 100.0d
執行個體分析
以下執行個體均使用JDK 1.8編譯,并使用javap生成位元組碼指令清單。
代碼1
void spin() {
int i;
for (i = ; i < ; i++) {
; // Loop body is empty
}
}
位元組碼指令序列
iinc用于實作局部變量的自增操作。在所有位元組碼指令中,隻有該指令可直接用于操作局部變量。
對于非-1至5的int類型常量(對應指令iconst_N),使用bipush來将單位元組常量值推至棧頂。
JVM對int類型提供了比較和跳轉相結合的if指令,例如該例子中的if_icmplt指令。而對于long、float和double,則需要先通過各自的cmp比較指令計算出int類型結果,再結合int類型的if指令判斷後再進行跳轉。
代碼2
void dspin() {
double i;
for (i = ; i < ; i++) {
; // Loop body is empty
}
}
位元組碼指令序列
其中,double類型占用局部變量的2個Slot,局部變量索引号從0開始,是以dstore_1對應的局部變量索引為1和2。
由于iinc隻針對int類型進行自增操作,JVM并沒有提供相應的指令來操作double類型。是以,需要借助dadd來實作double類型的自增操作。
同樣,以if開頭的比較跳轉指令,都隻用于int類型。但JVM另外提供了dcmpg、dcmpl來比較兩個double類型數值的大小,然後将比較結果(1,0,-1)壓入棧頂。最後,再使用int類型的if判斷指令來進行判斷跳轉。
dcmpg與dcmpl的差別僅在于,當比較的其中一個值為NaN時,dcmpg将1壓入棧頂,而dcmpl将-1壓入棧頂。
ldc相關指令都是将常量值從常量池中推至棧頂。
代碼3
void sspin() {
short i;
for (i = ; i < ; i++) {
; // Loop body is empty
}
}
位元組碼指令序列
short類型同樣需要通過多條指令來實作i++操作,對應于索引号為5至9的指令。首先,使用iadd實作2個int類型數值相加,再使用i2s指令将int類型結果強制轉換為short類型,最後使用istore_1指令将結果存回局部變量i。
對于byte、char和short類型資料,JVM并未提供像int類型一樣豐富的直接操作指令。然而,由于byte、char和short類型資料都可以自動寬化轉換為int類型,是以均可通過int類型的指令來操作。唯一額外的代價是要将操作結果截短至它們的有效範圍内。
參考
《Java虛拟機規範》(Java SE 8版)
《深入了解Java虛拟機 JVM進階特性與最佳實踐》
轉載請注明來源:http://zhanjia.iteye.com/blog/2430731
個人公衆号
二進制之路