上篇(JVM內(nèi)存與垃圾回收篇)大綱
- JVM與Java體系結(jié)構(gòu)
- 類加載子系統(tǒng)
- 運(yùn)行時(shí)數(shù)據(jù)區(qū)概述及線程
- 程序計(jì)數(shù)器(PC寄存器)
- 虛擬機(jī)棧
- 本地方法接口
- 本地方法棧
- 堆
- 方法區(qū)
- 直接內(nèi)存
- 執(zhí)行引擎
- StringTable
- 垃圾回收概述
- 垃圾回收算法
- 垃圾回收概念
- 垃圾回收器
程序計(jì)數(shù)器(PC寄存器)
目錄
- PC Register介紹
- 舉例說明
- 兩個(gè)常見問題
1. PC Register介紹
虛擬機(jī)規(guī)范官方地址:https://docs.oracle.com/javase/specs/jvms/se8/html/
官方規(guī)范的第5.2.1節(jié)就是關(guān)于PC Register的介紹。

JVM中的程序計(jì)數(shù)寄存器(Program Counter Register)中,Register的命名源于CPU的寄存器,寄存器存儲(chǔ)指令相關(guān)的現(xiàn)場(chǎng)信息。CPU只有把數(shù)據(jù)裝載到寄存器才能夠運(yùn)行。
這里,并非是廣義上所指的物理寄存器,或許將其翻譯為PC計(jì)數(shù)器(或指令計(jì)數(shù)器)會(huì)更加貼切(也稱為程序鉤子),并且也不容易引起一些不必要的誤會(huì)。JVM中的PC寄存器是對(duì)物理PC寄存器的一種抽象模擬。

作用:PC寄存器用來存儲(chǔ)指向下一條指令的地址,也即是將要執(zhí)行的指令代碼。由執(zhí)行引擎讀取下一條指令。
- 它是一塊很小的內(nèi)存空間,幾乎可以忽略不記。也是運(yùn)行速度最快的存儲(chǔ)區(qū)域。
- 在JVM規(guī)范中,每個(gè)線程都有它自己的程序計(jì)數(shù)器,是線程私有的,生命周期與線程的生命周期保持一致。
- 任何時(shí)間一個(gè)線程都只有一個(gè)方法在執(zhí)行,也就是所謂的當(dāng)前方法。程序計(jì)數(shù)器會(huì)存儲(chǔ)當(dāng)前線程正在執(zhí)行的Java方法的JVM指令地址;或者,如果是在執(zhí)行native方法,則是未指定值(undefined)。
- 它是程序控制流的指示器,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)數(shù)器來完成。
- 字節(jié)碼解釋器工作時(shí)就是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令。
- 它是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何outotMemoryError情況的區(qū)域。
2. 舉例說明(PC寄存器的使用舉例)
public class PCRegisterTest {
public static void main(String[] args) {
int i = 10;
int j = 20;
int k = i + j;
String s = "abc";
System.out.println(i);
System.out.println(k);
}
}
找到輸出目錄下的字節(jié)碼文件,執(zhí)行javap - verbose PC命令,查看反編譯的結(jié)果,部分如下:左側(cè)數(shù)字是指令地址(或叫偏移地址),右側(cè)是操作指令
stack=2, locals=5, args_size=1
0: bipush 10
2: istore_1
3: bipush 20
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: ldc #2 //String abc
12: astore 4
14: getstatic #3 //Field java/lang/System.out:Ljava/io/PrintStream;

執(zhí)行引擎去PC寄存器指向的下一個(gè)指令地址位置(比如圖3中5)取操作指令(圖中istore_2),其中涉及操作局部變量表、操作數(shù)棧,把字節(jié)碼指令翻譯成機(jī)器指令,讓CPU做運(yùn)算。
3. 兩個(gè)常見問題
3.1 第一個(gè)問題
- 使用PC寄存器存儲(chǔ)字節(jié)碼指令地址有什么用呢?
-
為什么使用PC寄存器記錄當(dāng)前線程的執(zhí)行地址呢?
4.png
因?yàn)镃PU需要不停的切換各個(gè)線程,這時(shí)候切換回來以后,就得知道接著從哪開始繼續(xù)執(zhí)行。
JVM的字節(jié)碼解釋器就需要通過改變PC寄存器的值來明確下一條應(yīng)該執(zhí)行什么樣的字節(jié)碼指令。
3.2 第二個(gè)問題
- PC寄存器為什么會(huì)被設(shè)定為線程私有?
我們都知道所謂的多線程在一個(gè)特定的時(shí)間段內(nèi)只會(huì)執(zhí)行其中某一個(gè)線程的方法,CPU會(huì)不停地做任務(wù)切換,這樣必然導(dǎo)致經(jīng)常中斷或恢復(fù),如何保證分毫無差呢?為了能夠準(zhǔn)確地記錄各個(gè)線程正在執(zhí)行的當(dāng)前字節(jié)碼指令地址,最好的辦法自然是為每一個(gè)線程都分配一個(gè)PC寄存器,這樣一來各個(gè)線程之間便可以進(jìn)行獨(dú)立計(jì)算,從而不會(huì)出現(xiàn)相互干擾的情況。
由于CPU時(shí)間片輪限制,眾多線程在并發(fā)執(zhí)行過程中,任何一個(gè)確定的時(shí)刻,一個(gè)處理器或者多核處理器中的一個(gè)內(nèi)核,只會(huì)執(zhí)行某個(gè)線程中的一條指令。
這樣必然導(dǎo)致經(jīng)常中斷或恢復(fù),如何保證分毫無差呢?每個(gè)線程在創(chuàng)建后,都會(huì)產(chǎn)生自己的程序計(jì)數(shù)器和棧幀,程序計(jì)數(shù)器在各個(gè)線程之間互不影響。
時(shí)間片
CPU時(shí)間片即CPU分配給各個(gè)程序的時(shí)間,每個(gè)線程被分配一個(gè)時(shí)間段,稱作它的時(shí)間片。
在宏觀上:我們可以同時(shí)打開多個(gè)應(yīng)用程序,每個(gè)應(yīng)用程序并行不悖,同時(shí)運(yùn)行。
但在微觀上:由于只有一個(gè)CPU,一次只能處理程序要求的一部分,如何處理公平,一種方法就是引入時(shí)間片,每個(gè)程序輪流執(zhí)行。
