JVM 运行时数据区域(Runtime Data Area)是 Java 虚拟机在执行 Java 程序过程中所管理的内存区域。需要注意的是,它与 Java 内存模型(Java Memory Model, JMM)是完全不同的概念。JVM 运行时数据区域主要由以下五部分组成:

1. 程序计数器(线程私有)
程序计数器(Program Counter Register)是一块较小的内存空间,可看作当前线程所执行字节码的行号指示器。如果当前线程正在执行 Java 方法,该计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行 Native 方法,则计数器值为 undefined。程序计数器是 JVM 规范中唯一没有规定任何 OutOfMemoryError 情况的区域。
JVM 通过线程轮流切换来获取 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。 - 执行第二条指令:执行
iload_1,将索引为 1 的整数(参数b)压入操作数栈。PC 更新为2。 - 执行第三条指令:执行
iadd,弹出栈顶两个整数相加,结果压回栈顶。PC 更新为3。 - 执行最后一条指令:执行
ireturn,弹出栈顶结果并返回。方法执行完毕。
若发生线程切换,各线程的 PC 值独立保存:
线程 A: PC = 2 // 下一条执行 iadd
线程 B: PC = 4 // 下一条执行 ireturn
2. Java 虚拟机栈(线程私有)
Java 虚拟机栈(JVM Stack)是线程私有的运行时数据区,生命周期与线程相同。它用于描述 Java 方法执行的内存模型:方法调用时,JVM 会创建一个**栈帧(Stack Frame)**并压入虚拟机栈;方法执行结束时,对应栈帧弹出。通常所说的'栈内存'即指此区域。

栈帧主要包含以下部分:
- 局部变量表(Local Variable Table):存储方法参数和内部定义的局部变量(基本类型和对象引用)。对象引用保存的是堆中对象的地址。其大小在编译期确定,执行期间不变,以
slot(槽位)形式顺序存放。 - 操作数栈:JVM 执行字节码指令时临时存放运算数据和结果的栈结构。指令通过入栈(push)和出栈(pop)完成计算,可视为 JVM 的'临时工作台'。
- 动态链接:在方法执行期间,将常量池中的符号引用解析为实际方法或字段引用的过程。
- 方法返回地址:当前方法执行结束后,程序返回到调用者方法继续执行的位置(即下一条待执行的字节码地址)。
3. 本地方法栈(线程私有)
本地方法栈(Native Method Stack)与 Java 虚拟机栈作用类似,区别在于:Java 虚拟机栈为 JVM 执行 Java 方法服务,而本地方法栈为 JVM 调用的 Native 方法服务。其异常类型与虚拟机栈相同,此处不再赘述。
4. 堆(线程共享)
堆(Heap)是 JVM 中用于存放对象实例和数组的运行时内存区域,所有线程共享。在 Java 中创建对象时,本质上就是在堆中申请内存。
User user = new User();
// 栈中保存引用 (reference),指向堆中的 User 对象实例
// 真正的对象数据存储在堆中,因此常说 Java 对象'在堆上分配'
堆为何必须共享? 假设堆不共享,每个线程拥有独立的对象副本,将导致数据无法同步。JVM 的设计原则明确:对象数据在堆中共享,执行上下文在栈中隔离。
常见的 JVM 堆内存参数:-Xms(初始堆大小)和 -Xmx(最大堆大小)。
堆也是垃圾收集器(GC)管理的主要区域,常被称为 GC 堆。基于分代收集算法,堆可细分为:
- 新生代:包含 Eden 区、From Survivor 区、To Survivor 区。
- 老年代:存放生命周期较长的对象。
细分的目的是为了更高效地分配与回收内存。
堆内存常见的 OutOfMemoryError 类型:
java.lang.OutOfMemoryError: GC Overhead Limit Exceeded:JVM 花费过多时间进行垃圾回收,但回收的内存极少。java.lang.OutOfMemoryError: Java heap space:创建新对象时,堆内存不足以分配空间。该错误与配置的 JVM 堆大小有关,而非物理内存不足。
5. 方法区(线程共享)
方法区(Method Area)是 JVM 规范中的逻辑区域,在不同 JDK 版本中有不同实现。JDK 7 及之前称为永久代(PermGen),JDK 8 起永久代被移除,由**元空间(Metaspace)**替代。
注意:方法区是规范概念,元空间是其具体实现方式之一,二者不能直接划等号。
方法区主要存储以下内容:
- 类的结构信息(元数据):JVM 加载类时,会将类名、父类、实现的接口、字段信息、方法信息及访问修饰符等解析后存入方法区。
- 运行时常量池:每个 class 文件包含常量池,存放字符串常量、类名、方法名、字段名及符号引用等。类加载后,常量池内容进入运行时常量池。例如
String s = "hello";中的"hello"会进入常量池(注:现代 JVM 中字符串对象实例在堆中,但常量池引用位于方法区/元空间)。 - 静态变量(static 变量):属于类级别的变量,不随对象实例分配在堆中,而是存放在方法区。
- JIT 编译后的代码:即时编译器(JIT)会将热点方法编译为本地机器码以提升执行效率,编译后的机器码也存储在方法区相关区域。

