🔊介绍

    JVM中的程序计数寄存器(Program Counter Register)中,寄存器存储指令相关的现场信息,CPU只有把数据装载到寄存器才能够运行。

    JVM中的程序计数器是对物理计算机寄存器的一种模拟,并非是指广义上所指的物理寄存器,也叫PC寄存器。

    🔨作用

    PC寄存器用来存储下一条指令的地址,即即将要执行的指令代码。由执行引擎读取下一条指令。

    程序计数器.svg

    📄详述

    • 它是一块很小的内存空间,几乎可以忽略不计。也是运行速度最快的存储区域。
    • 在JVM规范中,每个线程都有它自己的程序计数器,是现车给私有的,生命周期与线程的生命周期保持一致。
    • 任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址;或者,如果是在执行native方法,则是未指定值(undefined)。
    • 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
    • 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
    • 它是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

    ⌨️实例

    测试代码如下:

    1. public class PCRegisterTest {
    2. public static void main(String[] args) {
    3. int i = 10;
    4. int j = 20;
    5. int k = i + j;
    6. String s = "KHighness";
    7. System.out.println(k);
    8. System.out.println(s);
    9. }
    10. }

    编译后再反编译:javap -v PCRegisterTest.java

    结果如下:

    1. D:\Java\Test\Test\target\classes\top\parak\chapter01>javap -v PCRegisterTest.class
    2. Classfile /D:/Java/Test/Test/target/classes/top/parak/chapter01/PCRegisterTest.class
    3. Last modified 2020-10-31; size 722 bytes
    4. MD5 checksum 8a19ae86b495f9dd8ef414436e2baef5
    5. Compiled from "PCRegisterTest.java"
    6. public class top.parak.chapter01.PCRegisterTest
    7. minor version: 0
    8. major version: 52
    9. flags: ACC_PUBLIC, ACC_SUPER
    10. Constant pool:
    11. #1 = Methodref #7.#27 // java/lang/Object."<init>":()V
    12. #2 = String #28 // KHighness
    13. #3 = Fieldref #29.#30 // java/lang/System.out:Ljava/io/PrintStream;
    14. #4 = Methodref #31.#32 // java/io/PrintStream.println:(I)V
    15. #5 = Methodref #31.#33 // java/io/PrintStream.println:(Ljava/lang/String;)V
    16. #6 = Class #34 // top/parak/chapter01/PCRegisterTest
    17. #7 = Class #35 // java/lang/Object
    18. #8 = Utf8 <init>
    19. #9 = Utf8 ()V
    20. #10 = Utf8 Code
    21. #11 = Utf8 LineNumberTable
    22. #12 = Utf8 LocalVariableTable
    23. #13 = Utf8 this
    24. #14 = Utf8 Ltop/parak/chapter01/PCRegisterTest;
    25. #15 = Utf8 main
    26. #16 = Utf8 ([Ljava/lang/String;)V
    27. #17 = Utf8 args
    28. #18 = Utf8 [Ljava/lang/String;
    29. #19 = Utf8 i
    30. #20 = Utf8 I
    31. #21 = Utf8 j
    32. #22 = Utf8 k
    33. #23 = Utf8 s
    34. #24 = Utf8 Ljava/lang/String;
    35. #25 = Utf8 SourceFile
    36. #26 = Utf8 PCRegisterTest.java
    37. #27 = NameAndType #8:#9 // "<init>":()V
    38. #28 = Utf8 KHighness
    39. #29 = Class #36 // java/lang/System
    40. #30 = NameAndType #37:#38 // out:Ljava/io/PrintStream;
    41. #31 = Class #39 // java/io/PrintStream
    42. #32 = NameAndType #40:#41 // println:(I)V
    43. #33 = NameAndType #40:#42 // println:(Ljava/lang/String;)V
    44. #34 = Utf8 top/parak/chapter01/PCRegisterTest
    45. #35 = Utf8 java/lang/Object
    46. #36 = Utf8 java/lang/System
    47. #37 = Utf8 out
    48. #38 = Utf8 Ljava/io/PrintStream;
    49. #39 = Utf8 java/io/PrintStream
    50. #40 = Utf8 println
    51. #41 = Utf8 (I)V
    52. #42 = Utf8 (Ljava/lang/String;)V
    53. {
    54. public top.parak.chapter01.PCRegisterTest();
    55. descriptor: ()V
    56. flags: ACC_PUBLIC
    57. Code:
    58. stack=1, locals=1, args_size=1
    59. 0: aload_0
    60. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
    61. 4: return
    62. LineNumberTable:
    63. line 14: 0
    64. LocalVariableTable:
    65. Start Length Slot Name Signature
    66. 0 5 0 this Ltop/parak/chapter01/PCRegisterTest;
    67. public static void main(java.lang.String[]);
    68. descriptor: ([Ljava/lang/String;)V
    69. flags: ACC_PUBLIC, ACC_STATIC
    70. Code:
    71. stack=2, locals=5, args_size=1
    72. 0: bipush 10
    73. 2: istore_1
    74. 3: bipush 20
    75. 5: istore_2
    76. 6: iload_1
    77. 7: iload_2
    78. 8: iadd
    79. 9: istore_3
    80. 10: ldc #2 // String KHighness
    81. 12: astore 4
    82. 14: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
    83. 17: iload_3
    84. 18: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
    85. 21: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
    86. 24: aload 4
    87. 26: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    88. 29: return
    89. LineNumberTable:
    90. line 17: 0
    91. line 18: 3
    92. line 19: 6
    93. line 21: 10
    94. line 22: 14
    95. line 23: 21
    96. line 24: 29
    97. LocalVariableTable:
    98. Start Length Slot Name Signature
    99. 0 30 0 args [Ljava/lang/String;
    100. 3 27 1 i I
    101. 6 24 2 j I
    102. 10 20 3 k I
    103. 14 16 4 s Ljava/lang/String;
    104. }
    105. SourceFile: "PCRegisterTest.java"

    Code模块解析:

    image-20201031134509185.png

    ❓常见问题

    1. 为什么使用PC寄存器记录当前线程的执行地址?
      因为CPU需要不停的来回切换各个线程,这时候切换回来以后,就需要知道接着从哪开始继续执行。
      JVM的的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令。
    2. PC寄存器为什么会被设定为线程私有?
      为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法就是为每一个线程都分配一个PC寄存器,这样一来各个线程之间就可以进行独立计算,从而不会出现相互干扰的情况。
      由于CPU时间片轮限制,众多线程再并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。
      这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响。