【JVM之記憶體與垃圾回收篇】程式計數器
程式計數器
介紹
JVM 中的程式計數暫存器(Program Counter Register)中,Register 的命名源於 CPU 的暫存器,暫存器儲存指令相關的現場資訊。CPU 只有把資料裝載到暫存器才能夠執行。
這裡,並非是廣義上所指的物理暫存器,或許將其翻譯為 PC 計數器(或指令計數器)會更加貼切(也稱為程式鉤子),並且也不容易引起一些不必要的誤會。JVM 中的 PC 暫存器是對物理 PC 暫存器的一種抽象模擬。
它是一塊很小的記憶體空間,幾乎可以忽略不記。也是執行速度最快的儲存區域。
在 JVM 規範中,每個執行緒都有它自己的程式計數器,是執行緒私有的,生命週期與執行緒的生命週期保持一致。
任何時間一個執行緒都只有一個方法在執行,也就是所謂的當前方法。程式計數器會儲存當前執行緒正在執行的 Java 方法的 JVM 指令地址;或者,如果是在執行 native 方法,則是未指定值(undefned)。
它是程式控制流的指示器,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成。位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令。
它是唯一一個在 Java 虛擬機器器規範中沒有規定任何 OutOfMemoryError 情況的區域。
作用
作用:PC 暫存器用來儲存指向下一條指令的地址,也即將要執行的指令程式碼。由執行引擎讀取下一條指令。
程式碼演示
我們首先寫一個簡單的程式碼
/**程式計數器
* @author: Nemo
*/
public class PCRegisterTest {
public static void main(String[] args) {
int i = 10;
int j = 20;
int k = i + j;
}
}
然後將程式碼進行編譯成位元組碼檔案,我們再次檢視,發現在位元組碼的左邊有一個行號標識,它其實就是指令地址,用於指向當前執行到哪裡。
0: bipush 10
2: istore_1
3: bipush 20
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: return
通過 PC 暫存器,我們就可以知道當前程式執行到哪一步了!
使用 PC 暫存器儲存位元組碼指令地址有什麼用呢?
因為 CPU 需要不停的切換各個執行緒,這時候切換回來以後,就得知道接著從哪開始繼續執行。
JVM 的位元組碼直譯器就需要通過改變 PC 暫存器的值來明確下一條應該執行什麼樣的位元組碼指令。
PC 暫存器為什麼被設定為私有的?
我們都知道所謂的多執行緒在一個特定的時間段內只會執行其中某一個執行緒的方法,CPU 會不停地做任務切換,這樣必然導致經常中斷或恢復,如何保證分毫無差呢?為了能夠準確地記錄各個執行緒正在執行的當前位元組碼指令地址,最好的辦法自然是為每一個執行緒都分配一個 PC 暫存器,這樣一來各個執行緒之間便可以進行獨立計算,從而不會出現相互幹擾的情況。
由於 CPU 時間片輪限制,眾多執行緒在併發執行過程中,任何一個確定的時刻,一個處理器或者多核處理器中的一個核心,只會執行某個執行緒中的一條指令。
這樣必然導致經常中斷或恢復,如何保證分毫無差呢?每個執行緒在建立後,都會產生自己的程式計數器和棧幀,程式計數器在各個執行緒之間互不影響。
CPU 時間片
CPU 時間片即 CPU 分配給各個程式的時間,每個執行緒被分配一個時間段,稱作它的時間片。
在巨集觀上:我們可以同時開啟多個應用程式,每個程式並行不悖,同時執行。
但在微觀上:由於只有一個 CPU,一次只能處理程式要求的一部分,如何處理公平,一種方法就是引入時間片,每個程式輪流執行。