1. 程式人生 > >JVM 棧幀之運算元棧與區域性變量表

JVM 棧幀之運算元棧與區域性變量表

目錄

  • 前置知識
  • 引子
    • 基於暫存器的設計模式
    • 基於棧的設計模式
  • 一個簡單的例子
    • 如何檢視區域性變量表?
    • 例項方法中的區域性變量表
  • 結論

前置知識

閱讀本文需要對以下知識有所瞭解:
* 棧
* 彙編
* Java 基礎
* 逆波蘭表示式(有學過的同學閱讀本文毫無障礙)

引子

基於暫存器的設計模式

就我們所熟知的x86或arm指令集來說,其對資料的操作都是基於暫存器。例如,要對兩個數執行加法操作則需要將這兩個數分別送入兩個暫存器再執行加法操作,這也符合我們對於程式語言認知,更加易於理解。

基於棧的設計模式

基於棧的設計模式則是將資料存放在棧中,在需要使用的時候將棧頂的資料出棧,並執行相應的操作。

舉例來說,在JVM中 執行 a = b + c 的位元組碼執行過程中運算元棧以及區域性變量表的變化如下圖所示。

區域性變量表中儲存著a、b、c 三個區域性變數,首先將b和c分別入棧

將棧頂的兩個數出棧執行加法操作,並將結果儲存至棧頂,之後將棧頂的數出棧賦值給a

一個簡單的例子

在上一節中我們瞭解了棧與區域性變量表是如何配合完成一次加法操作的,這一節我們將對區域性變量表進行深入的研究。

如何檢視區域性變量表?

我們可以通過反編譯class檔案的方式檢視區域性變量表,不過在這裡更加推薦使用IDEA的jclasslib外掛(直接搜就有)檢視位元組碼,因為其設計更加人性化,更加友好。

例項方法中的區域性變量表

我們知道在例項方法中我們可以直接訪問例項的成員變數或函式,而不需要通過this來引用,這是如何實現的?
像這種問題直接動手寫個測試類,反編譯一下結果自然就清晰了。
首先來一個簡單的類

public class T {

    private int a = 0;

    public void add(int b,int c){
        a = b + c;
    }
}

其次,將該類選中,然後在view中選中showbytecode with jclasslib

效果如下:

選擇我們關注的Methods中add方法,注意觀察圖中高亮的部分

原來,JVM在編譯程式碼的時候,偷偷在區域性變量表中添加了一個this引用(很明顯this儲存的例項的引用),這也是我們為什麼可以在方法中訪問例項中的成員變數的原因,證明如下

圖中節碼的簡要解釋如下:
0)aload_0 將this的引用入棧 (aload_0即將區域性變量表中索引為0的引用壓到運算元棧中)
1)iload_1 將引數b入棧 (將區域性變數中的索引為1的整數壓到運算元棧中)
2)iload_2 將引數c入棧

此時棧的內容有(0為棧頂)
0.c
1.b
2.this

3)iadd 將棧頂的兩個數相加,並將結果儲存至棧頂,此時棧的內容為
0.b+c
1.this

4). putfield 將棧頂的兩個值出棧,第一個值(b+c)賦值給第二個值(this)的對應的成員變數(是的,沒錯即使是賦值也要執行兩次出棧操作)
putfield的說明如下(注意圖中的高亮部分):

地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.putfield

結論

基於棧的指令集系統可以很方便的做到平臺無關性(x86、arm),但也降低了效能,這也是為啥Java效能比C低原因。(操作暫存器快,還是操作棧快?哈哈哈哈哈哈哈哈哈哈哈)