JVM 运行时数据区域也叫内存布局,但需要注意的是它和 Java 内存模型(Java Memory Model,简称 JMM)完全不同,属于完全不同的两个概念,它由以下 5 大部分组成:

1. 程序计数器(线程私有)
程序计数器的作用:用来记录当前线程执行的行号的。 程序计数器是一块比较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。如果当前线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个 Native 方法,这个计数器值为 undefined。程序计数器内存区域是唯一一个在 JVM 规范中没有规定任何 OOM 情况的区域!
在 JVM 中,多线程是通过线程轮流切换来获得 CPU 执行时间的,因此,在任一具体时刻,一个 CPU 的内核只会执行一条线程中的指令,因此,为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,并且不能互相干扰,否则就会影响到程序的正常执行次序。也就是说,我们要求程序计数器是线程私有的。
接下来我们通过一段非常简单的小代码以及字节码指令来看看程序计数器的作用。
public class PcDemo {
public static int add(int a, int b) {
return a + b;
}
}
字节码指令大致如下:
0: iload_0 // 从局部变量表中加载变量 a 到操作数栈
1: iload_1 // 从局部变量表中加载变量 b 到操作数栈
2: iadd // 两数相加
3: ireturn // 返回
- 初始状态:当方法开始执行时,PC 计数器设置为 0,指向第一条指令 0: iload_0。
- 执行第一条指令:执行 iload_0 指令,将局部变量表中索引为 0 的整数(即方法的第一个参数 a)加载到操作数栈顶。执行完成后,PC 计数器更新为 1,指向下一条指令 1: iload_1。
- 执行第二条指令:执行 iload_1 指令,将局部变量表中索引为 1 的整数(即方法的第二个参数 b)加载到操作数栈顶。执行完成后,PC 计数器更新为 2,指向下一条指令 2: iadd。
- 执行第三条指令:执行 iadd 指令,弹出操作数栈顶的两个整数(即 a 和 b),将它们相加,然后将结果压入操作数栈顶。执行完成后,PC 计数器更新为 3,指向下一条指令 3: ireturn。
- 执行最后一条指令:执行 ireturn 指令,弹出操作数栈顶的整数(即 a + b 的结果),并将这个值作为方法的返回值。方法执行完成,控制权返回到方法调用者。
如果 JVM 线程发生切换:
线程 A PC=2 // 线程 A 下一条指令应该执行 iadd
线程 B PC= // 线程 下一条指令应该执行 ireturn



