第 4 章 一 程序计数器、为什么使用程序计数器来记录当前线程的执行地址呢?

第 4 章 程序计数器 (PC寄存器)

1、PC寄存器概述 (重点)

文档网址

PC寄存器介绍 以及 作用 :

  • 线程私有, 运行速度最快的存储区域, 程序计数器存储当前线程正在执行的Java方法的JVM指令地址
  • PC寄存器不会出现OOM(OutofMemoryError) 内存溢出, StackOverFlow 栈溢出 的情况
  • 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
  • 当CPU并发执行, cpu时间片轮训执行不同的线程(或者发生中断操作), 使用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情况的区域

image-20200705155551919

PC 寄存器的作用

  • PC寄存器用来存储指向下一条指令的地址也即将要执行的指令代码由执行引擎将指令解释为机器码交由cpu进行处理

image-20200705155728557

2、代码示例

  • 代码
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);

    }
}
  • 反编译:javap -v xxx.class
// 常量池
Constant pool:
   #1 = Methodref          #6.#26         // java/lang/Object."<init>":()V
   #2 = String             #27            // abc
   #3 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Methodref          #30.#31        // java/io/PrintStream.println:(I)V
   #5 = Class              #32            // com/atguigu/java/PCRegisterTest
   #6 = Class              #33            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/atguigu/java/PCRegisterTest;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               i
  #19 = Utf8               I
  #20 = Utf8               j
  #21 = Utf8               k
  #22 = Utf8               s
  #23 = Utf8               Ljava/lang/String;
  #24 = Utf8               SourceFile
  #25 = Utf8               PCRegisterTest.java
  #26 = NameAndType        #7:#8          // "<init>":()V
  #27 = Utf8               abc
  #28 = Class              #34            // java/lang/System
  #29 = NameAndType        #35:#36        // out:Ljava/io/PrintStream;
  #30 = Class              #37            // java/io/PrintStream
  #31 = NameAndType        #38:#39        // println:(I)V
  #32 = Utf8               com/atguigu/java/PCRegisterTest
  #33 = Utf8               java/lang/Object
  #34 = Utf8               java/lang/System
  #35 = Utf8               out
  #36 = Utf8               Ljava/io/PrintStream;
  #37 = Utf8               java/io/PrintStream
  #38 = Utf8               println
  #39 = Utf8               (I)V
{
  public com.atguigu.java.PCRegisterTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/atguigu/java/PCRegisterTest;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      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;
        17: iload_1
        18: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
        21: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        24: iload_3
        25: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
        28: return
      LineNumberTable:
        line 10: 0
        line 11: 3
        line 12: 6
        line 14: 10
        line 15: 14
        line 16: 21
        line 18: 28
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      29     0  args   [Ljava/lang/String;
            3      26     1     i   I
            6      23     2     j   I
           10      19     3     k   I
           14      15     4     s   Ljava/lang/String;
  • 左边的数字代表指令地址(指令偏移),即 PC寄存器中可能存储的值,然后执行引擎读取 PC 寄存器中的值,并执行该指令

image-20200727212024900

3、两个面试题 (重要)

  • 使用PC寄存器存储字节码指令地址有什么用呢?
  • 为什么使用 PC 寄存器来记录当前线程的执行地址呢?
  1. 因为线程是一个个的顺序执行流CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行
  2. JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令
    image-20200705161409533

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

  • 总结下面: 为了保证当并发执行的多个线程的时候, cpu时间片疯狂切换, 当切换到别的线程的时候, 我们要记录上一个线程执行到哪个指令了. 所以使用pc寄存器来存储; 如果设置为线程共享的pc寄存器, 当切换到上一个线程的时候, 它恢复到原来执行的位置. 就不准确了
  1. 我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法(并发操作),CPU会不停地做任务切换,这样必然导致经常中断或恢复,如何保证分毫无差呢?
  2. 为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。
  3. 由于CPU时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。
  4. 这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器栈帧,程序计数器在各个线程之间互不影响

4、CPU 时间片

  • 并发执行的时候: 宏观上来说, 同时执行; 微观上来说, 串行执行
  1. CPU时间片即CPU分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片。
  2. 宏观上:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行
  3. 微观上:由于只有一个CPU(当有多核,多个CPU的时候, 真正意义上存在并行, 但也存在并发的现象.),一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行
    image-20200705161849557
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

white camel

感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值