什么叫做“程序计数器”?它能做什么?
程序计数器在JMM中的位置如下
是一个线程独享的数据区域,也就是说,每个线程之间的数据相互不干扰,是一个线程内存空间。
程序计数器 内存空间是不大的,第一眼看到这个名词,很有可能被认为是计量程序执行次数?不不不,千万别这样理解,我们可以把它当做一个下标位置临时存储,例如打断点中的当前断点位置;都知道,对于jvm层面而言,所执行的是class字节码文件,那么字节码文件中的程序逻辑也有顺序,一个简单的程序化结构语言如下:
那么程序计数器中到底存放的是什么内容呢?
好比下方的一个简单的程序流程图来说,当执行完“用户输入”动作后,下一步的动作是什么?谁知道?只有当前线程的程序计数器知道,"程序计数器"知道,当“输入是否是字符Y”后判断true还是false后,分别执行说明动作?是B操作?C操作?还是其他操作?都需要通过程序计数器来控制。
换一种描述词,我们还可以把其当做当前线程所执行的字节码的行号指示器。是一种程序控制流的指示器,包括任何的分支、循环、跳转、异常处理、线程恢复等操作都需要依赖于当前线程的程序计数器来指引。
通过程序计数器,将一个个指令,可以连接成一个完整的动作,从而完成整个程序的执行。
那么为什么程序计数器的内存空间是私有的呢?
这里就要涉及到多线程环境下的问题了。
在我们的物理机上,可能会存在上百个线程在同时执行,那么每个线程所执行的工作都是不一样的。线程1正在执行开门动作,下一步准备执行关门动作;线程2正在执行扫地动作,下一步准备执行倒垃圾动作;那么两个线程之间的下一步动作都不能相互干扰,不然就乱套了。
在我们的物理机上,会有多核处理器(或者单核处理器),但是只会有一个内核,那么在某一个时刻,只会有一个线程抢占到了内核执行权利,也就是说,某一个时刻只会执行一个线程所做的事情,那么在多线程环境下,线程间的来回切换是很常见的事情,多线程之间频繁竞争处理器的执行所有权,可能线程1刚执行完开门动作,刚开到一半的门,就被线程2所抢占了,就开始扫地,线程2扫了一下,又被线程1抢占,继续执行开门动作了。这种频繁的线程唤醒,线程挂起的动作,在每个线程都会高频出现,那么为了保证每个线程在唤起的时候,能够正确的恢复到各自的执行位置,也就是对应的行号指示器的位置,继续执行未完成的动作,那么每个线程都需要有一个独立的程序计数器空间,用于存储各自线程的执行指令控制,保证多线程间相互不影响。这类内存空间也统称为“线程私有”的内存。
当Java程序正在执行 “本地方法(Native Method)”的时候,程序计数器是null?
结合网络大佬的说法,我们也分析一下:
1.当执行Java程序的时候,我们程序计数器中存的就是Java字节码的地址。
存放的地址有两种形式:
(1)相对下一步指令的字节码开始处的偏移量:bytecode index, bci,字节码下标;
(2)该字节码指令在jvm内存中的地址值:bytecode pointer, bcp;
2.但是对于native method 而言,方法体内容不是由Java程序语言编写的,自然在解释编译阶段,也不能生成对应的字节码数据。所以jvm规范上定义了,如果当前执行的方法是本地方法,那么对应计数器所指向的值是未定义的,也就是可能为null。
那么问题来了,如果是null,那么执行了一个本地方法后,接下来要执行什么呢?这如何绑定呢?
这里就是涉及到一个线程映射问题了,我们的jvm线程是通过映射方式到操作系统上线程执行的。那么当执行的是一个本地方法的时候,就跳过了映射,直接在系统上执行,并不需要考虑JVM的程序计数器的概念。那么本地方法执行完成后,就会退出当前栈帧,接着就继续执行后一个方法。是不需要程序计数器提供数据支撑的。本身就是一个操作系统层面的概念了。