天天看點

JVM 運作時記憶體空間詳解——程式計數器

文章目錄

        • 一、什麼是程式計數器(PC寄存器)
        • 二、PC寄存器有哪些特點
        • 三、多個線程,如何确定執行到某個位置進行恢複呢

通過上一篇文章,我們大體了解了JVM的整體架構,其分為:中繼資料(JDK7是方法區)、堆、虛拟機棧、本地方法棧、程式計數器幾個部分。

JVM 運作時記憶體空間詳解——程式計數器

本篇文章,咱們對程式計數器進行剖析,一探究竟。

一、什麼是程式計數器(PC寄存器)

程式計數器(Program Counter Register): 也叫PC寄存器,是一塊較小的記憶體空間,它可以看做是目前線程所執行的位元組碼的行号訓示器。

在虛拟機的概念模型裡,位元組碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、循環、跳轉、異常處理、線程恢複等基礎功能都需要依賴這個計數器來完成。

概念不好了解?那麼通過代碼方式來看下,

java代碼:

public class PCRegister {
    public static void main(String[] args) {
        int x = 1;
        int y = 2;
        System.out.println(x+y);
    }
}
           

為了了解其機制,首先,我們得檢視反編譯後的位元組碼檔案。

如何檢視?有以下兩種方式,

方式1(控制台指令):

  1. 右鍵位元組碼檔案打開控制台終端:
    JVM 運作時記憶體空間詳解——程式計數器
  2. 在控制台輸入指令:javap -v 位元組碼檔案名稱
    JVM 運作時記憶體空間詳解——程式計數器
  3. 紅框裡的内容即為位元組碼檔案反編譯後的内容
    JVM 運作時記憶體空間詳解——程式計數器

方式2(IDEA插件):

  1. 安裝 jclasslib Bytecode Viewer 插件
    JVM 運作時記憶體空間詳解——程式計數器
    2.打開位元組碼檔案 PCRegister.class -> 點選 View -> 點選 Show Bytecode with Jclasslib
    JVM 運作時記憶體空間詳解——程式計數器
  2. 選中方法 -> 選中main -> 選中 Code, 即可檢視位元組碼反編譯後的内容
    JVM 運作時記憶體空間詳解——程式計數器
    分析:
    JVM 運作時記憶體空間詳解——程式計數器

如上圖,位元組碼檔案反編譯後可以看到有一系列 指令位址和 操作指令。

要想讓計算機執行程式,需要讓執行引擎中的解釋器将位元組碼操作指令解釋成CPU能夠識别的機器指令。

而選取哪一條操作指令進行解釋并執行,這個時候就需要依賴于程式計數器了。可以把它想象成一個臨時空間,用于存儲位元組碼操作指令的指令位址。

本圖中,0 就是一個指令位址,通過指令位址就能夠找到哪條指令,說明目前需要選取執行的操作指令是:iconst_1。

如果執行完0後,需要執行指向1的這條指令,那麼将程式計數器(PC計數器)中存儲的指令位址改成1就行了。

二、PC寄存器有哪些特點

  1. 差別于計算機硬體的pc寄存器,兩者不略有不同。計算機用pc寄存器來存放“僞指令”或位址,而相對于虛拟機,pc寄存器它表現為一塊記憶體,虛拟機的pc寄存器的功能也是存放僞指令,更确切的說存放的是将要執行指令的位址。
  2. 當虛拟機正在執行的方法是一個本地(native)方法的時候,jvm的pc寄存器存儲的值是undefined。
  3. 程式計數器是線程私有的,它的生命周期與線程相同,每個線程都有一個。
  4. 此記憶體區域是唯一一個在Java虛拟機規範中沒有規定任何OutOfMemoryError 情況的區域。

三、多個線程,如何确定執行到某個位置進行恢複呢

JVM 運作時記憶體空間詳解——程式計數器
  • Java虛拟機的多線程是通過線程輪流切換并配置設定處理器執行時間的方式來實作的,在任何一個确定的時刻,一個處理器隻會執行一條線程中的指令。
  • 是以,為了線程切換後能恢複到正确的執行位置,每條線程都需要有一個獨立的程式計數器,各條線程之間的計數器互不影響,獨立存儲,我們稱這類記憶體區域為“線程私有”的記憶體。

下面,舉個栗子:

JVM 運作時記憶體空間詳解——程式計數器

如上圖所示,目前有兩個線程,因為程式計數器線程私有,是以兩個線程中各有一個程式計數器。

當Thread1 執行完指令2後,此時程式計數器儲存的指令位址是3。此時時間片剛好切到 Thread2, Thread2開始執行,執行完指令6後,程式計數器的指令位址儲存為7。

此時時間片再次切回 Thread1 , Thread1直接找到原來存儲的指令3進行執行。

同理,後續如果切回Thread2線程,Thread2會找到原本存儲的指令7進行執行。

這就是線程執行到某個位置的恢複,主要是依據程式計數器的值來實作的。