本篇爲《JVM指令分析實例》的第五篇,相關實例均使用Oracle JDK 1.8編譯,並使用javap生成字節碼指令清單。
前幾篇傳送門:
預備知識
局部變量表的變量槽(Variable Slot)
局部變量表的容量以變量槽(Variable Slot)爲最小單位,虛擬機規範中並沒有明確指明一個Slot應占用的內存空間大小。
每個Slot能存放一個boolean、byte、char、short、int、float、reference或returnAddress類型的數據。
reference類型表示對一個對象實例的引用,虛擬機規範沒有說明它的長度及結構。
returnAddress類型目前已經很少見了,它是爲字節碼指令jsr、jsr_w和ret服務的,指向了一條字節碼指令的地址。
對於64位的數據類型(long、double),虛擬機會以高位對齊的方式爲其分配兩個連續的Slot空間。
操作數棧管理指令
複製指令實例代碼
package jvm.specification.se8.chapter3;
public class NextIndex {
private long index = 0;
public long nextIndex() {
return index++;
}
}
字節碼指令序列
public long nextIndex():
0: aload_0 // 將第1個局部變量this壓入棧頂
1: dup // 複製棧頂this並壓入棧頂. 棧底到棧頂:this、this
2: getfield #12 // Field index:J. 獲取實例字段index並壓入棧頂,消耗棧頂的1個this. 棧底到棧頂:this、index_for_ladd
5: dup2_x1 // 複製棧頂index數值,並插入第1個this下面. 棧底到棧頂:index_for_return、this、index_for_ladd
6: lconst_1 // 將long類型常量1壓入棧頂
7: ladd // 將棧頂的2個long類型數值相加,並將結果壓入棧頂. 棧底到棧頂:index_for_lreturn、this、index_for_putfield
8: putfield #12 // Field index:J. 將棧頂數值賦值給實例字段index
11: lreturn
Constant pool:
#1 = Class #2 // jvm/specification/se8/chapter3/NextIndex
#2 = Utf8 jvm/specification/se8/chapter3/NextIndex
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 index
#6 = Utf8 J
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Methodref #3.#11 // java/lang/Object."<init>":()V
#11 = NameAndType #7:#8 // "<init>":()V
#12 = Fieldref #1.#13 // jvm/specification/se8/chapter3/NextIndex.index:J
#13 = NameAndType #5:#6 // index:J
dup2_x1指令
複製棧頂的1個或2個值,並將其插入到棧頂的2個或3個值下面。
在預備知識中,我們對局部變量的Slot做了簡單說明。可以簡單理解爲,long類型和double類型佔2個Slot,其他類型佔1個Slot。
下面拆解一下指令的定義(借用局部變量的Slot概念來描述,有點不太嚴謹,但易於理解與記憶。)。
複製棧頂的1個或2個值:
1個可以是long類型和double類型,2個是其他類型,共2個Slot。
插入到棧頂的2個或3個值下面:
如果複製的是1個值(即棧頂是long或double,共2個Slot),那麼插入到棧頂的2個值(棧頂1個long或double,下面1個其他類型,共3個Slot)下面。
如果複製的是2個值(即棧頂是2個其他類型數值,共2個Slot),那麼插入到棧頂的3個值(棧頂3個都是其他類型,共3個Slot)下面。
簡單理解,dup2_x1指令的作用就是將棧頂的2個Slot的值複製並插入到棧頂的3個Slot的值下面。
對於本實例,執行dup2_x1指令之前的棧結構爲(棧底到棧頂):this、index。由於index爲long類型,佔2個Slot。this爲引用類型,佔1個Slot。因此,dup2_x1指令將棧頂的2個Slot的index值複製並插入到棧頂的3個Slot的this引用下面。
操作數棧之指令係數法
dup總共有6個指令,分別是dup、dup_x1、dup_x2、dup2、dup2_x1和dup2_x2。初看這些指令,容易混淆而難以理解。經過分類和找規律,可以通過"指令係數法"來理解記憶,非常簡單:
- 不帶_x的指令是複製棧頂數據並壓入棧頂。包括兩個指令,dup和dup2
- 帶_x的指令是複製棧頂數據並插入棧頂以下的某個位置。共有4個指令
-
dup的係數代表要複製的Slot個數。
- dup開頭的指令用於複製1個Slot的數據。例如1個int或1個reference類型數據
- dup2開頭的指令用於複製2個Slot的數據。例如1個long,或2個int,或1個int+1個float類型數據
-
對於帶_x的複製插入指令,只要將指令的dup和x的係數相加,結果即爲需要插入的位置。因此
- dup_x1插入位置:1+1=2,即棧頂2個Slot下面。
- dup_x2插入位置:1+2=3,即棧頂3個Slot下面。
- dup2_x1插入位置:2+1=3,即棧頂3個Slot下面。
- dup2_x2插入位置:2+2=4,即棧頂4個Slot下面。
操作數棧管理指令共有9個,上面已經介紹了6個。剩下的3個用同樣的方法就很容易理解了:
- pop:將棧頂的1個Slot數值出棧。例如1個short類型數值
- pop2:將棧頂的2個Slot數值出棧。例如1個double類型數值,或者2個int類型數值
- swap:交換棧頂的2個Slot數值位置。Java虛擬機沒有提供交換兩個64位數據類型(long、double)數值的指令。
備註:指令係數法是自己爲了方便記憶起的名字
參考
《The Java Virtual Machine Specification, Java SE 8 Edition》
《Java虛擬機規範》(Java SE 8版)
《深入理解Java虛擬機 JVM高級特性與最佳實踐》
轉載請註明來源:http://zhanjia.iteye.com/blog/2432142
個人公衆號
二進制之路