一、PC 寄存器概述
PC 寄存器介绍
-
JVM中的程序计数寄存器(Program Counter Register)中,Register的命名源于CPU的寄存器,寄存器存储指令相关的现场信息。CPU只有把数据装载到寄存器才能够运行。
-
这里,并非是广义上所指的物理寄存器,或许将其翻译为PC计数器(或指令计数器)会更加贴切(也称为程序钩子),并且也不容易引起一些不必要的误会。JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟。
-
它是一块很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域。
-
在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致。
-
任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址;或者,如果是在执行native方法,则是未指定值(undefned)。
-
它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
-
它是唯一一个在Java虚拟机规范中没有规定任何OutofMemoryError情况的区域。
PC 寄存器的作用
PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎将指令解释为机器码交由cpu进行处理
二、代码示例
- 代码
1 public class PCRegisterTest { 2 3 public static void main(String[] args) { 4 int i = 10; 5 int j = 20; 6 int k = i + j; 7 8 String s = "abc"; 9 10 System.out.println(k); 11 System.out.println(s); 12 } 13 }
- 反编译:javap -v xxx.class
1 MacBook-Pro-22907:jvm h__d$ pwd 2 /Users/h__d/Documents/workspace-idea/test-jvm/out/production/day04/com/test/jvm 3 MacBook-Pro-22907:jvm h__d$ javap -v PCRegisterTest.class 4 Classfile /Users/h__d/Documents/workspace-idea/test-jvm/out/production/day04/com/test/jvm/PCRegisterTest.class 5 Last modified 2020-12-11; size 702 bytes 6 MD5 checksum c27960ecaf15720393724a60083d4e13 7 Compiled from "PCRegisterTest.java" 8 public class com.test.jvm.PCRegisterTest 9 minor version: 0 10 major version: 52 11 flags: ACC_PUBLIC, ACC_SUPER 12 Constant pool: 13 #1 = Methodref #7.#27 // java/lang/Object."<init>":()V 14 #2 = String #28 // abc 15 #3 = Fieldref #29.#30 // java/lang/System.out:Ljava/io/PrintStream; 16 #4 = Methodref #31.#32 // java/io/PrintStream.println:(I)V 17 #5 = Methodref #31.#33 // java/io/PrintStream.println:(Ljava/lang/String;)V 18 #6 = Class #34 // com/test/jvm/PCRegisterTest 19 #7 = Class #35 // java/lang/Object 20 #8 = Utf8 <init> 21 #9 = Utf8 ()V 22 #10 = Utf8 Code 23 #11 = Utf8 LineNumberTable 24 #12 = Utf8 LocalVariableTable 25 #13 = Utf8 this 26 #14 = Utf8 Lcom/test/jvm/PCRegisterTest; 27 #15 = Utf8 main 28 #16 = Utf8 ([Ljava/lang/String;)V 29 #17 = Utf8 args 30 #18 = Utf8 [Ljava/lang/String; 31 #19 = Utf8 i 32 #20 = Utf8 I 33 #21 = Utf8 j 34 #22 = Utf8 k 35 #23 = Utf8 s 36 #24 = Utf8 Ljava/lang/String; 37 #25 = Utf8 SourceFile 38 #26 = Utf8 PCRegisterTest.java 39 #27 = NameAndType #8:#9 // "<init>":()V 40 #28 = Utf8 abc 41 #29 = Class #36 // java/lang/System 42 #30 = NameAndType #37:#38 // out:Ljava/io/PrintStream; 43 #31 = Class #39 // java/io/PrintStream 44 #32 = NameAndType #40:#41 // println:(I)V 45 #33 = NameAndType #40:#42 // println:(Ljava/lang/String;)V 46 #34 = Utf8 com/test/jvm/PCRegisterTest 47 #35 = Utf8 java/lang/Object 48 #36 = Utf8 java/lang/System 49 #37 = Utf8 out 50 #38 = Utf8 Ljava/io/PrintStream; 51 #39 = Utf8 java/io/PrintStream 52 #40 = Utf8 println 53 #41 = Utf8 (I)V 54 #42 = Utf8 (Ljava/lang/String;)V 55 { 56 public com.test.jvm.PCRegisterTest(); 57 descriptor: ()V 58 flags: ACC_PUBLIC 59 Code: 60 stack=1, locals=1, args_size=1 61 0: aload_0 62 1: invokespecial #1 // Method java/lang/Object."<init>":()V 63 4: return 64 LineNumberTable: 65 line 3: 0 66 LocalVariableTable: 67 Start Length Slot Name Signature 68 0 5 0 this Lcom/test/jvm/PCRegisterTest; 69 70 public static void main(java.lang.String[]); 71 descriptor: ([Ljava/lang/String;)V 72 flags: ACC_PUBLIC, ACC_STATIC 73 Code: 74 stack=2, locals=5, args_size=1 75 0: bipush 10 76 2: istore_1 77 3: bipush 20 78 5: istore_2 79 6: iload_1 80 7: iload_2 81 8: iadd 82 9: istore_3 83 10: ldc #2 // String abc 84 12: astore 4 85 14: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 86 17: iload_3 87 18: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 88 21: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 89 24: aload 4 90 26: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 91 29: return 92 LineNumberTable: 93 line 6: 0 94 line 7: 3 95 line 8: 6 96 line 10: 10 97 line 12: 14 98 line 13: 21 99 line 14: 29 100 LocalVariableTable: 101 Start Length Slot Name Signature 102 0 30 0 args [Ljava/lang/String; 103 3 27 1 i I 104 6 24 2 j I 105 10 20 3 k I 106 14 16 4 s Ljava/lang/String; 107 } 108 SourceFile: "PCRegisterTest.java"
- 左边的数字代表指令地址(指令偏移),即 PC 寄存器中可能存储的值,然后执行引擎读取 PC 寄存器中的值,并执行该指令
三、面试题
3.1、使用PC寄存器存储字节码指令地址有什么用呢?
-
因为线程是一个个的顺序执行流,CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行
-
JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令
3.2、PC寄存器为什么被设定为私有的?
-
我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停地做任务切换,这样必然导致经常中断或恢复,如何保证分毫无差呢?
-
为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。
-
由于CPU时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。
-
这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响。