JVM指令分析實例一(常量、局部變量、for循環)

Java虛擬機的指令由一個字節長度的、表明着某種特定操做含義的操做碼以及跟隨其後的零至多個表明此操做所需參數的操做數所構成。虛擬機中許多指令並不包含操做數,只有一個操做碼。java

Java虛擬機限制操做碼的長度爲1個字節,所以最多隻能有256個指令。數組

指令格式

如下指令格式,是基於Oracle JDK編譯後,經過javap工具生成的指令描述格式。bash

<index> <opcode> [<operand1> [<operand2>...]] [<comment>]
複製代碼

<index>工具

指令操做碼在方法字節碼指令數組中的索引,也能夠認爲是相對於方法起始處的字節偏移量。其中,指令數組指方法對應的Code屬性的code[]數組,該數組用於存放方法的字節碼指令。oop

該索引能夠做爲控制轉移指令的跳轉目標。例如,goto 8指令表示跳轉到索引爲8的指令上繼續執行。spa

<opcode>3d

指令的操做碼助記符。例如,iconst_0、istore_一、iload_1和return等。code

<operandN>cdn

指令操做數,一條指令能夠有0至多個操做數。例如,iconst_0沒有操做數,bipush有1個操做數,iinc有2個操做數。blog

<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高級特性與最佳實踐》

我的公衆號

更多文章,請關注公衆號:二進制之路

二進制之路
相關文章
相關標籤/搜索