天天看點

JVM-運作時資料區之PC寄存器

1.運作時資料區圖

運作時資料區是在類加載完成後所經曆的階段,當我們通過前面的:類的加載 --> 驗證 --> 準備 --> 解析 --> 初始化,這幾個階段完成後,執行引擎就會對類進行使用,這時就用到了運作時資料區。

舉例來說,類的加載過程就好像是買菜的過程,經過一系列奔波,從購買到檢驗,最後再送到廚房(也就是運作時資料區)。而執行引擎就是一名廚師,他會用準備好的蔬菜去進行菜品的制作。

2.程式計數器(PC寄存器)

官方文檔網址:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html

2.1 PC寄存器介紹

  • JVM中的程式計數寄存器(Program Counter Register)中,Register的命名源于CPU的寄存器,寄存器存儲指令相關的現場資訊。CPU隻有把資料裝載到寄存器才能夠運作。
  • 這裡,并非是廣義上所指的實體寄存器,或許将其翻譯為PC計數器(或指令計數器)會更加貼切(也稱為程式鈎子),并且也不容易引起一些不必要的誤會。JVM中的PC寄存器是對實體PC寄存器的一種抽象模拟。
  • 它是一塊很小的記憶體空間,幾乎可以忽略不記。也是運作速度最快的存儲區域。
  • 在JVM規範中,每個線程都有它自己的程式計數器,是線程私有的,生命周期與線程的生命周期保持一緻。
  • 任何時間一個線程都隻有一個方法在執行,也就是所謂的目前方法。程式計數器會存儲目前線程正在執行的Java方法的JVM指令位址;或者,如果是在執行native方法,則是未指定值(undefned)。
  • 它是程式控制流的訓示器,分支、循環、跳轉、異常處理、線程恢複等基礎功能都需要依賴這個計數器來完成。
  • 位元組碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令。
  • 它是唯一一個在Java虛拟機規範中沒有規定任何OutofMemoryError情況的區域。

2.2 PC寄存器的作用

PC寄存器用來存儲指向下一條指令的位址,也即将要執行的指令代碼。由執行引擎讀取下一條指令,并執行該指令。

static {};
    descriptor: ()V
    flags: (0x0008) ACC_STATIC
    Code:
      stack=3, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #3                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
        10: invokestatic  #5                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
        13: invokevirtual #6                  // Method java/lang/Thread.getName:()Ljava/lang/String;
        16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: ldc           #8                  // String 初始化目前類
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        30: goto          30
      LineNumberTable:
        line 22: 0
        line 23: 30
      StackMapTable: number_of_entries = 1
        frame_type = 30 /* same */
}

           

上面位元組碼中左邊的數字代表指令位址(指令偏移),即 PC 寄存器中可能存儲的值,然後執行引擎讀取 PC 寄存器中的值,并執行該指令,中間的就是指令,後面的#+數字則是運作時常量池中的符号引用,具體後面會說。

面試題

1.使用PC寄存器存儲位元組碼指令位址有什麼用呢?

  • 因為CPU需要不停的切換各個線程,這時候切換回來以後,就得知道接着從哪開始繼續執行
  • JVM的位元組碼解釋器就需要通過改變PC寄存器的值來明确下一條應該執行什麼樣的位元組碼指令

2.PC寄存器為什麼被設定為私有的?

  • 我們都知道所謂的多線程在一個特定的時間段内隻會執行其中某一個線程的方法,CPU會不停地做任務切換,這樣必然導緻經常中斷或恢複,如何保證分毫無差呢?為了能夠準确地記錄各個線程正在執行的目前位元組碼指令位址,最好的辦法自然是為每一個線程都配置設定一個PC寄存器,這樣一來各個線程之間便可以進行獨立計算,進而不會出現互相幹擾的情況。
  • 由于CPU時間片輪限制,衆多線程在并發執行過程中,任何一個确定的時刻,一個處理器或者多核處理器中的一個核心,隻會執行某個線程中的一條指令。
  • 這樣必然導緻經常中斷或恢複,如何保證分毫無差呢?每個線程在建立後,都會産生自己的程式計數器和棧幀,程式計數器在各個線程之間互不影響。

擴充:

CPU 時間片

  • CPU時間片即CPU配置設定給各個程式的時間,每個線程被配置設定一個時間段,稱作它的時間片。
  • 在宏觀上:我們可以同時打開多個應用程式,每個程式并行不悖,同時運作。
  • 但在微觀上:由于隻有一個CPU,一次隻能處理程式要求的一部分,如何處理公平,一種方法就是引入時間片,每個程式輪流執行。

并行,并發

  • 并行是指多個處理器或者是多核的處理器同時處理多個不同的任務。
  • 而并發是指兩個或多個任務在同一時間間隔執行(線程來回切換)。
jvm