Java 執行過程中的記憶體模型

小z同學發表於2021-01-31

一、前言

本文的主要工作:嘗試以時間順序追蹤一遍 Java 執行的整個過程,以及展示 JVM 中記憶體模型的相應變化。

本文的主要目的:希望能夠通過 Java 執行過程的冰山一角,增進對程式語言工作原理的理解。

以下面這段程式碼為例,追蹤它的執行過程:

public class Car {
    private int speed;

    public void setSpeed(int speed) {
        this.speed = speed;
    }

    public void getSpeed() {
        System.out.println(speed);
    }

    public static void main(String[] args) {
        Car car = new Car();
        car.setSpeed(3);
        car.getSpeed();
    }
}

二、執行過程

接下來是具體的執行過程,總共包含五個步驟:編譯、載入、執行 main 方法、執行成員方法、方法返回。

Step1:編譯

首先,在我們完成上述這段原始碼之後,要想讓程式跑起來,我們需要將其編譯成為位元組碼檔案。位元組碼是一種跨平臺的JVM機器語言,它能夠被JVM所解析,而無關底層的作業系統。

Step2:載入

當程式碼需要被呼叫時,JVM 會載入目標位元組碼至方法區,並轉化為方法區的執行時資料結構,這裡的載入過程是通過類載入器完成的。然後記憶體中(不一定是堆)會生成一個代表這個類的 java.lang.Class 物件,作為方法區這個類的各種資料結構的訪問入口。

Step3:執行 main 方法

main 方法可以通過 java.lang.Class 物件進行呼叫,參考如下程式碼:

Method method = targetClass.getDeclareMethod("main", String[].class);
method.invoke(null, (Object) new String[0]);

之後 PC 暫存器將會指向方法區中的 main 函式地址,執行緒棧中會生成對應的棧楨,其主要用於存放當前方法的區域性變數表、操作棧、以及方法返回地址。接下來,PC 暫存器向後地址偏移,執行引擎開始執行 main 方法體。當語句 Car car = new Car() 執行完畢,棧楨與堆中的相應變化如下:

Step4:執行成員方法

物件 car 的 setSpeed 方法呼叫過程和 main 類似,通過索引 car 的成員方法地址,PC暫存器將指向方法區中的 setSpeed 函式地址,同時執行緒棧中將產生新的棧楨,其中的方法返回地址用於儲存原有 PC 地址偏移。當賦值語句 this.speed = speed 執行完畢,棧楨與堆中發生的相應變化如下:

Step5:方法返回

隨著 setSpeed 方法的執行結束,Stack 中的相應棧楨出棧,棧頂指標重新指向 main 棧楨。同時 PC 暫存器將根據方法返回地址進行還原,從而繼續執行 main 的方法體。當 main 方法也執行完畢出棧後,主執行緒與虛擬機器例項銷亡,程式結束。

三、雜談

虛擬機器或某一門程式語言,作為一種底層實現,可以滿足上層使用者的絕大部分需求,但是需求是與時俱進的,總有一天使用者需要編寫自己的底層實現,比如元件、框架、一門新語言。這時需要開啟原有的規範,先破壞它,再重建它,從而定義自己的規範。這也許是我們需要探究底層的緣由之一吧。


參考連結

[1] <<深入理解Java虛擬機器>>
[2] https://www.cnblogs.com/zzzz76/p/9282981.html
[3] https://www.cnblogs.com/zzzz76/p/8150862.html
[4] https://www.cnblogs.com/zzzz76/p/8076536.html

相關文章