JVM指令分析例項一(常量、區域性變數、for迴圈)
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
個人公眾號
二進位制之路