在 Java 面试中,JVM 内存结构几乎是必问知识点。很多人能背出程序计数器、虚拟机栈、本地方法栈、堆和方法区这五个区域,但当面试官追问它们的作用、线程共享关系以及对象为什么在堆上时,往往难以解释清楚。本文将从 JVM 的整体内存布局出发,系统梳理五大运行时数据区域的作用与常见面试考点。
JVM 运行时数据区域也叫内存布局,但需注意它和 Java 内存模型(Java Memory Model,简称 JMM)完全不同,属于两个不同的概念。它由以下 5 大部分组成:

程序计数器:线程执行的导航仪
作用:记录当前线程执行的行号。这块内存空间较小,可视为当前线程所执行字节码的行号指示器。如果当前线程正在执行的是 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,指向下一条指令 。



