程序计数器:线程私有的执行引擎
你知道 CPU 是怎么知道下一条该执行什么指令的吗?
在物理 CPU 中,程序计数器(PC)是一个寄存器,存放着下一条指令的地址。JVM 中的程序计数器,本质上是一样的——只不过 JVM 运行的是字节码,而不是机器码。
一、它的作用是什么?
程序计数器(Program Counter Register),也称为 PC 寄存器,是一小块内存空间。
每个线程都有自己的程序计数器,互不干扰,这与 CPU 的设计完全一致。
它的作用很明确:
- 存储当前指令地址:如果当前执行的是 Java 方法,存储的是正在执行的字节码指令的地址
- 支持分支、循环、跳转:字节码执行
goto、if_icmp等指令时,需要修改 PC 的值 - 恢复执行位置:线程切换时,保存当前执行位置;切换回来时,从保存的位置继续执行
public class PCRegisterDemo {
public int method() {
int a = 1;
int b = 2;
int c = a + b;
if (c > 10) {
return c;
}
return 0;
}
}当 JVM 执行这个方法时,程序计数器会依次记录每条字节码指令的位置。当 if_c > 10 为 true 时,PC 会跳转到 return c 对应的指令位置。
二、Native 方法的特殊情况
如果执行的是 Native 方法(通过 JNI 调用本地代码),程序计数器的值是 undefined(未定义)。
这是因为 Native 方法通常调用的是 C/C++ 代码,这些代码由操作系统直接执行,不归 JVM 管理。
Native 方法调用
┌─────────────────────────────────────┐
│ Java 代码执行区 │
│ PC 寄存器 → 字节码地址 │
└─────────────────────────────────────┘
↓ JNI 调用
┌─────────────────────────────────────┐
│ Native 代码执行区 │
│ PC 寄存器 → undefined │
│ (由操作系统管理) │
└─────────────────────────────────────┘三、唯一一个没有 OOM 的区域
在 JVM 的所有内存区域中,程序计数器是唯一一个不会抛出 OutOfMemoryError 的区域。
为什么?
因为它的空间是固定的——对于一个线程来说,程序计数器只需要存储一个指针(返回地址或字节码地址)。无论你怎么折腾 JVM,都不可能让程序计数器耗尽内存。
| 区域 | 可能出现的异常 | 原因 |
|---|---|---|
| 程序计数器 | 无 | 空间固定,不存在 OOM |
| 虚拟机栈 | StackOverflowError / OOM | 栈深度或线程数超限 |
| 堆 | OutOfMemoryError | 对象过多 |
| 方法区 | OutOfMemoryError | 类过多 |
四、线程切换与执行位置保存
理解了程序计数器的作用,你就能理解线程切换是怎么工作的:
时间线:
T1: 线程A执行 → PC = 10(记录位置)
↓ 线程切换(时间片用完)
T2: 线程B执行 → PC = 25(记录位置)
↓ 线程切换(时间片用完)
T3: 线程A恢复 → 读取 PC = 10,继续执行当操作系统决定切换线程时,JVM 会:
- 保存线程 A 的程序计数器(PC = 10)
- 恢复线程 B 的程序计数器(PC = 25)
- 切换到线程 B 执行
这就是为什么程序计数器必须是线程私有的——每个线程的执行进度不同,必须独立记录。
五、面试常考点
问题 1:什么情况下 PC 寄存器的值是 undefined?
执行 Native 方法时。Native 方法由 C/C++ 实现,执行权交给操作系统,JVM 不再记录字节码位置。
问题 2:程序计数器会GC吗?
不会。程序计数器是线程私有的,随线程创建而创建,随线程消亡而释放(或者被回收)。它不参与垃圾回收。
问题 3:为什么需要程序计数器?
多线程环境下,CPU 在不同线程间切换。当线程重新获得 CPU 时间片时,需要知道从哪里继续执行——程序计数器就是用来记录这个位置的。
留给你的问题
程序计数器存储的是字节码指令地址,而不是指令本身。
你有没有想过:为什么 JVM 选择存储地址,而不是把整个指令都存下来?
实际上,这是冯·诺依曼架构的基本思想——代码(指令)和数据是分开存储的,程序计数器本质上是一个"指针",指向代码区。
但这也引出了一个问题:如果两个线程同时执行同一个方法,它们的程序计数器会一样吗?
答案是:会的,但各自独立。就像同一个教室里有两个人同时看同一本书,各自在自己的便签上标记"看到第几页"——书是同一本书,但标记是各自的。
下一节,我们来聊聊虚拟机栈——这里才是存储方法调用细节的地方。
