參考:https://www.cnblogs.com/newAndHui/p/11878504.html
一、定義
Program Counter Register 程序計數器(寄存器)(線程獨享):程序計數器是一塊 較小 的內存空間,它可以看做是當前線程所執行的字節碼的 行號指示器 ;在虛擬機的概念模型里(僅僅是概念模型,各種虛擬機可能會通過一些更高效的方式去實現),字節碼解釋器工作時,就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳准、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成 ;
- 作用:記錄正在執行的虛擬機字節碼指令的地址(如果正在執行的是本地方法則為空)
- 特點:
- 是線程私有的
- 不會存在內存溢出
- 生命周期隨着線程,線程啟動而產生,線程結束而消亡
二、特點詳解
理解:程序計數器,可以看做是當前線程執行的字節碼的 行號指示器
//當java 文件被翻譯為字節碼的時候,字節碼大概類似於下面的樣子
public void test() {
0 xxxx ..
2 xxxx ..
4 xxxx ..
5 xxxx ..
}
上面左邊的0、2、4、5,就是類似於字節碼的行號(實際是指令的偏移地址),程序計數器中保存的值就是它們;字節碼解釋器,就是根據它們來執行程序的。
三、深入了解
java是支持多線程的,當CPU執行權從 A 線程,轉移到 B 線程的時候,JVM就要暫時掛起線程 A ,去執行線程 B ;當線程 A 再次得到CPU執行權的時候,又會掛起B線程,繼續執行 A 線程 ;
1、CPU是怎么記住之前的A線程,執行到哪一處的:
CPU根本就不會記住之前執行到哪里了,它只是埋頭苦干;
2、那是什么保證了切換線程的程序可以正常執行的:
程序計數器 ;程序計數器里面保存的是 當前線程執行的字節碼的行號(看着像行號,其實是指令地址);
3、那么,我們需要幾個程序計數器呢:(每個線程都需要有一個獨立的程序計數器)
如果只有一個的話,切換B線程以后,程序計數器里面保存的就是B線程所執行的字節碼的行號了,再切換回A線程,就蒙圈了,不知道執行到哪里了,因為,程序計數器里面保存的是B線程當前執行的字節碼地址 ;因此,我們可以想象出,要為每個線程都分配一個程序計數器,因此,程序計數器的內存空間是線程私有的 ;這樣即使線程 A 被掛起,但是線程 A 里面的程序計數器,記住了A線程當前執行到的字節碼的指令地址了 ,等再次切回到A線程的時候,看一下程序計數器,就知道之前執行到哪里了。
4、那么程序計數器,什么時候分配內存呢:
一個線程在執行的任何期間,都會失去CPU執行權,因此,我們要從一個線程被創建開始執行,就要無時無刻的記錄着該線程當前執行到哪里了!因此,線程計數器,必須是線程被創建開始執行的時候,就要一同被創建
5、程序計數器為什么不會存在內存溢出:
程序技術器保存的是當前執行的字節碼的偏移地址,當執行到下一條指令的時候,改變的只是程序計數器中保存的地址,並不需要申請新的內存來保存新的指令地址,因此,永遠都不可能內存溢出;因此,jvm虛擬機規范,也就沒有規定,也是唯一一個沒有規定 OutOfMemoryError 異常 的區域(即在同一塊內存上更改字節碼的偏移地址,不需要再創建新的內存來保存)
6、為什么當線程執行的是本地方法的時候,程序計數器中保存的值是空:
因為本地方法是 C++/C 寫的,由系統調用,根本不會產生字節碼文件,因此,程序計數器也就不會做任何記錄