JVM 程序计数器
程序计数器简介
JVM 中的程序计数器(Program Counter Register)是对物理 PC 寄存器的一种抽象模拟,用来存储指向当前线程正在执行的字节码地址(偏移地址)。
CUP 时间片
时间片是 CPU 分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片。如果在时间片结束时进程还在运行,则 CPU 将被剥夺并分配给另一个进程;如果进程在时间片结束前阻塞或结束,CPU 会立即进行切换,因而不会造成 CPU 资源浪费。
程序计数器的作用
程序执行的最小单位是线程,线程 A 和线程 B 对 CPU 的时间片资源是抢占式争夺,任何一个线程在使用 CPU 的时候,其他线程就被挂起,无法使用CPU。当一个优先级更高的线程要抢占当前 CPU 资源时,当前线程可能未执行完毕就被挂起,此时就需要保存现场信息,以便当前线程再次抢到 CPU 资源后继续执行,程序计数器的作用就是保存现场信息。CPU 需要不停的切换各个线程,切换回来得知道接着从哪里开始继续执行,JVM 的字节码解释器需要通过改变程序计数器的值来明确应该执行什么样的字节码指令。
程序计数器的特点
- 线程隔离性,每个线程工作时都有属于自己的独立程序计数器;
- 执行 Java 方法时,程序计数器是有值的,且记录的是正在执行的字节码指令的偏移地址;
- 执行 native 本地方法时,程序计数器的值为空(Undefined)。因为 native 方法是 Java 通过 JNI 直接调用本地 C/C++ 库,可以近似的认为 native 方法相当于 C/C++ 暴露给 Java 的一个接口,Java 通过这个接口调用 C/C++ 方法。由于该方法是通过 C/C++ 进行实现的,无法产生相应的字节码,并且 C/C++ 执行时的内存分配是由自己语言决定的,而不是由 JVM 决定的。
- 程序计数器占用内存很小,在进行 JVM 内存计算时,可以忽略不计,也是运行速度最快的存储区域;
- 在JVM 规范中,每个线程都有自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致;
- 程序计数器,是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 的区域(因为程序计数器存储的是字节码文件的行号,而这个范围是可知晓的,在一开始分配内存时就可以分配一个绝对不会溢出的内存);
- 任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的 Java 方法的 JVM 指令地址,或者如果是正在执行 native 方法,则是未指定值(undefined);
- 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成;
- 字节码解释器工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
具体实例说明
编写以下代码(PCRegisterTest.java)
package test; public class PCRegisterTest { public static void main(String[] args){ int i = 100; int j = 200; int n = i + j; String s = "abcd"; System.out.println(n); System.out.println(s); } }
对代码进行编译,并打开终端窗口(Terminal),输入以下命令
cd out/production/javaTest/test # 切换到 PCRegisterTest.class 所在目录
javap -v PCRegisterTest.class # 对 PCRegisterTest.class 文件进行反解析
可以反解析出 PCRegisterTest 类的汇编指令、本地变量表、代码行偏移量映射表、常量池等信息
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8 Classfile /D:/javaTest/out/production/javaTest/test/PCRegisterTest.class Last modified 2021-4-25; size 688 bytes MD5 checksum b841ba62c749080c5c5ddbecb72bbc96 Compiled from "PCRegisterTest.java" public class test.PCRegisterTest minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #7.#27 // java/lang/Object."<init>":()V #2 = String #28 // abcd #3 = Fieldref #29.#30 // java/lang/System.out:Ljava/io/PrintStream; #4 = Methodref #31.#32 // java/io/PrintStream.println:(I)V #5 = Methodref #31.#33 // java/io/PrintStream.println:(Ljava/lang/String;)V #6 = Class #34 // test/PCRegisterTest #7 = Class #35 // java/lang/Object #8 = Utf8 <init> #9 = Utf8 ()V #10 = Utf8 Code #11 = Utf8 LineNumberTable #12 = Utf8 LocalVariableTable #13 = Utf8 this #14 = Utf8 Ltest/PCRegisterTest; #15 = Utf8 main #16 = Utf8 ([Ljava/lang/String;)V #17 = Utf8 args #18 = Utf8 [Ljava/lang/String; #19 = Utf8 i #20 = Utf8 I #21 = Utf8 j #22 = Utf8 n #23 = Utf8 s #24 = Utf8 Ljava/lang/String; #25 = Utf8 SourceFile #26 = Utf8 PCRegisterTest.java #27 = NameAndType #8:#9 // "<init>":()V #28 = Utf8 abcd #29 = Class #36 // java/lang/System #30 = NameAndType #37:#38 // out:Ljava/io/PrintStream; #31 = Class #39 // java/io/PrintStream #32 = NameAndType #40:#41 // println:(I)V #33 = NameAndType #40:#42 // println:(Ljava/lang/String;)V #34 = Utf8 test/PCRegisterTest #35 = Utf8 java/lang/Object #36 = Utf8 java/lang/System #37 = Utf8 out #38 = Utf8 Ljava/io/PrintStream; #39 = Utf8 java/io/PrintStream #40 = Utf8 println #41 = Utf8 (I)V #42 = Utf8 (Ljava/lang/String;)V { public test.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 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Ltest/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 100 2: istore_1 3: sipush 200 6: istore_2 7: iload_1 8: iload_2 9: iadd 10: istore_3 11: ldc #2 // String abcd 13: astore 4 15: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 18: iload_3 19: invokevirtual #4 // Method java/io/PrintStream.println:(I)V 22: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 25: aload 4 27: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 30: return LineNumberTable: line 5: 0 line 6: 3 line 7: 7 line 8: 11 line 9: 15 line 10: 22 line 11: 30 LocalVariableTable: Start Length Slot Name Signature 0 31 0 args [Ljava/lang/String; 3 28 1 i I 7 24 2 j I 11 20 3 n I 15 16 4 s Ljava/lang/String; } SourceFile: "PCRegisterTest.java"