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>
指令行尾的註釋。註釋內容通常以//開始。
每一行中,表示運行時常量池索引的操作數前,會有一個井號。在指令後的註釋中,會帶有對這個操作數的描述,例如:
1: invokespecial #8 // Method java/lang/Object."<init>":()V
10: ldc2_w #19 // double 100.0d
實例分析
以下實例均使用JDK 1.8編譯,並使用javap生成字節碼指令清單。
代碼1
void spin() {
int i;
for (i = 0; i < 100; 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 = 0.0; i < 100.0; 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 = 0; i < 100; 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
個人公衆號
二進制之路