程序计数器是什么(PC)?

在学习操作系统、JVM的时都可以看到程序计数器(PC)的身影,但是对于程序计数器的概念一直是模模糊糊,这篇就稍微讲一下什么是PC?

一、JVM层面

什么叫做“程序计数器”?它能做什么?

程序计数器在JMM中的位置如下:

1.1 程序计数器的基本概念

  • 是一个线程私有的数据区域,也就是说,每个线程之间的数据相互不干扰,是一个线程内存空间。

  • 程序计数器 内存空间是不大的,第一眼看到这个名词,很有可能被认为是计量程序执行次数?不不不,千万别这样理解,我们可以把它当做一个下标位置临时存储,例如打断点中的当前断点位置;

  • 对于JVM层面而言,所执行的是class字节码文件,那么字节码文件中的程序逻辑也有顺序。

  • 那么程序计数器中到底存放的是什么内容呢?

    1. 好比下方的一个简单的程序流程图来说,当执行完“用户输入”动作后,下一步的动作是什么?谁知道?只有当前线程的程序计数器知道,"程序计数器"知道,当“输入是否是字符Y”后判断true还是false后,分别执行说明动作?是B操作?C操作?还是其他操作?都需要通过程序计数器来控制。

    2. 换一种描述词,我们还可以把其当做当前线程所执行的字节码的行号指示器。是一种程序控制流的指示器,包括任何的分支、循环、跳转、异常处理、线程恢复等操作都需要依赖于当前线程的程序计数器来指引。

  • 通过程序计数器,将一个个指令,可以连接成一个完整的动作,从而完成整个程序的执行。

1.2 为什么程序计数器的内存空间是私有的呢?

这里就要涉及到多线程环境下的问题了。

  • 在我们的物理机上,可能会存在上百个线程在同时执行,那么每个线程所执行的工作都是不一样的。线程1正在执行开门动作,下一步准备执行关门动作;线程2正在执行扫地动作,下一步准备执行倒垃圾动作;那么两个线程之间的下一步动作都不能相互干扰,不然就乱套了。

  • 在我们的物理机上,会有多核处理器(或者单核处理器),但是只会有一个内核,那么在某一个时刻,只会有一个线程抢占到了内核执行权利。

  • 也就是说,某一个时刻只会执行一个线程所做的事情,那么在多线程环境下,线程间的来回切换是很常见的事情,多线程之间频繁竞争处理器的执行所有权,可能线程1刚执行完开门动作,刚开到一半的门,就被线程2所抢占了,就开始扫地,线程2扫了一下,又被线程1抢占,继续执行开门动作了。

  • 这种频繁的线程唤醒,线程挂起的动作,在每个线程都会高频出现,那么为了保证每个线程在唤起的时候,能够正确的恢复到各自的执行位置,也就是对应的行号指示器的位置,继续执行未完成的动作,那么每个线程都需要有一个独立的程序计数器空间,用于存储各自线程的执行指令控制,保证多线程间相互不影响。这类内存空间也统称为“线程私有”的内存。

1.3 在Java程序 “本地方法(Native Method)”,程序计数器是null?

结合网络大佬的说法,我们也分析一下:

  • 当执行Java程序的时候,我们程序计数器中存的就是Java字节码的地址。

     存放的地址有两种形式:

     (1)相对下一步指令的字节码开始处的偏移量:bytecode index, bci,字节码下标;

     (2)该字节码指令在 JVM内存 中的地址值:bytecode pointer, bcp;

  • 但是对于native method 而言,方法体内容不是由Java程序语言编写的,自然在解释编译阶段,也不能生成对应的字节码数据。

    所以JVM规范上定义了,如果当前执行的方法是本地方法,那么对应计数器所指向的值是未定义的,也就是可能为null。

那么问题来了,如果是null,那么执行了一个本地方法后,接下来要执行什么呢?这如何绑定呢?

  • 这里就是涉及到一个线程映射问题了,我们的 JVM线程是通过映射方式到操作系统上线程执行的。

  • 那么当执行的是一个本地方法的时候,就跳过了映射,直接在系统上执行,并不需要考虑JVM的程序计数器的概念。

  • 那么本地方法执行完成后,就会退出当前栈帧,接着就继续执行后一个方法。是不需要程序计数器提供数据支撑的。本身就是一个操作系统层面的概念了。

二、操作系统层面

程序计数器是什么 PC(program counter)?

在CPU控制部件中的程序计数器(PC)的功能是用于存放指令的地址。程序执行时,PC的初值为程序第一条指令的地址,在顺序执行程序时,控制器首先按程序计数器所指出的指令地址从内存中取出一条指令,然后分析和执行该指令,同时将PC的值加1指向下一条要执行的指令。

2.1 上下文切换

我们都知道系统为了实现并发,操作系统采取了一种高层形式的异常控制流(ECF) 上下文切换,其基本思想就是在程序遇到阻塞,耗时间的系统调用或中断(周期性定时器中断)时CPU会对当前工作线程进行替换,当这些耗时的操作完成时再进行把跳回前面的程序段,这个跳就很有意思了,用户栈,内核栈,状态寄存器等不用说,数据当然该被记录,但操作系统又如何知道代码运行到哪里呢 这就是程序计数器的作用 我们再来看看程序计数器的含义程序计数器(PC)的功能是用于存放指令的地址 这样一想 便清晰了不少

2.2 非本地跳转

非本地跳转是一种用户级的异常控制流 其作用与goto类似 但与goto有一点不同 就是非本地跳转可跨函数进行跳转,其实跳转与上下文切换在某种意义上很相像,他们都要把当前状态转化为另外一种状态,这就牵扯到应该执行到哪一条指令的问题,这也是我们程序计数器的作用

2.3 fork()函数

在 fork 之后父子进行从同一指令开始运行,这是因为子进程拷贝了父进程的程序计数器,而程序计数器的作用是什么,就是存放当前指令 所以就不难理解了。

在三个简单的例子后我们已经大概的理解了程序计数器的作用是什么 接下来我们就来看看程序计数器的处理流程

在程序开始执行前,必须将它的起始地址,即程序的一条指令所在的内存单元地址送入PC,因此程序计数器(PC)的内容即是从内存提取的第一条指令的地址。当执行指令时,CPU将自动修改PC的内容,即每执行一条指令PC增加一个量,这个量等于指令所含的字节数,以便使其保持的总是将要执行的下一条指令的地址。由于大多数指令都是按顺序来执行的,所以修改的过程通常只是简单的对PC加1。

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值