JVM指令分析實例五(操作數棧)

本篇爲《JVM指令分析實例》的第五篇,相關實例均使用Oracle JDK 1.8編譯,並使用javap生成字節碼指令清單。

前幾篇傳送門:

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

JVM指令分析實例二(算術運算、常量池、控制結構)

JVM指令分析實例三(方法調用、類實例)

JVM指令分析實例四(數組、switch)

預備知識

局部變量表的變量槽(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

 

個人公衆號

二進制之路

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章