跳到主要内容

4. 程序计数器

4.1 PC Register介绍

Oracle官方PC寄存器介绍

2.5.1. The pc Register

The Java Virtual Machine can support many threads of execution at once (JLS §17). Each Java Virtual Machine thread has its own pc (program counter) register. At any point, each Java Virtual Machine thread is executing the code of a single method, namely the current method (§2.6) for that thread. If that method is not native, the pc register contains the address of the Java Virtual Machine instruction currently being executed. If the method currently being executed by the thread is native, the value of the Java Virtual Machine's pc register is undefined. The Java Virtual Machine's pc register is wide enough to hold a returnAddress or a native pointer on the specific platform.

#1000

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

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

4.1.1 作用

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

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

4.2 举例说明

public class PcRegisterTest {

public static void main(String[] args) {
int i = 10;
int j = 20;
int k = i + j;
}
}
java

再引入一些其他的结构。

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);
}
}
java
笔记

字节码中的 #n 对应的是常量池(Constant pool)中的内容。如上面序号为 10 的字节码 10: ldc #2 对应的就是常量池中的 #2 = String #27#27 又对应常量池中的 #27 = Utf8 abc

4.3 两个常见问题

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

4.4 CPU时间片

CPU时间片即CPU分配各各个程序的时间,每个线程被分配一个时间段。称作它的时间片。

  • 在宏观上:我们可以同时打开多个应用程序,每个程序并行不悖,同时运行。
  • 但在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。

4.4.1 并行与并发

  • 并行:同一时间多个线程同时执行;
  • 并发:一个核快速切换多个线程,让它们依次执行,看起来像并行,实际上是并发