【JVM】程序计数器(四)

一、PC 寄存器概述

  PC 寄存器介绍  

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

  2. 这里,并非是广义上所指的物理寄存器,或许将其翻译为PC计数器(或指令计数器)会更加贴切(也称为程序钩子),并且也不容易引起一些不必要的误会。JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟

  3. 它是一块很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域

  4. 在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致

  5. 任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址;或者,如果是在执行native方法,则是未指定值(undefned)。

  6. 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

  7. 它是唯一一个在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寄存器存储字节码指令地址有什么用呢?  

  1. 因为线程是一个个的顺序执行流,CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行

  2. JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令

3.2、PC寄存器为什么被设定为私有的?  

  1. 我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停地做任务切换,这样必然导致经常中断或恢复,如何保证分毫无差呢?

  2. 为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。

  3. 由于CPU时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。

  4. 这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响。

原文地址:https://www.cnblogs.com/h--d/p/14121605.html