JVM指令分析例項五(運算元棧)
本篇為《JVM指令分析例項》的第五篇,相關例項均使用Oracle JDK 1.8編譯,並使用javap生成位元組碼指令清單。
前幾篇傳送門:
ofollow,noindex" target="_blank">JVM指令分析例項一(常量、區域性變數、for迴圈)
預備知識
區域性變量表的變數槽(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 = Utf8jvm/specification/se8/chapter3/NextIndex #3 = Class#4// java/lang/Object #4 = Utf8java/lang/Object #5 = Utf8index #6 = Utf8J #7 = Utf8<init> #8 = Utf8()V #9 = Utf8Code #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
個人公眾號
二進位制之路