JVM 内存模型与运行时数据存储对应关系
| 内存区域 | 存储内容 | 生命周期 | 线程隔离性 | GC 管理 |
|---|---|---|---|---|
| 堆 (Heap) | 所有通过 new 创建的对象实例 数组对象 字符串常量池(JDK7+) | 对象存活到被 GC 回收 | 线程共享 | 是(主要回收区域) |
| 虚拟机栈 (JVM Stack) | 方法调用的栈帧: - 局部变量(基本类型、对象引用) - 方法参数 - 操作数栈 | 随方法调用创建,方法结束销毁 | 线程私有(每个线程独立) | 否 |
| 方法区 (Method Area) | 类元信息(Class 结构) 运行时常量池(字符串字面量、符号引用) 静态变量(static) | 类加载时分配,卸载时回收 | 线程共享 | 是(JDK8+ 由元空间管理) |
| 程序计数器 (PC Register) | 当前线程执行的字节码指令地址 | 线程生命周期内存在 | 线程私有 | 否 |
| 本地方法栈 (Native Stack) | Native 方法(C/C++ 实现)的调用信息 | 随本地方法调用创建,结束销毁 | 线程私有 | 否 |
各区域详细说明与代码示例
1. 堆 (Heap)
-
存储内容:所有对象实例和数组。
-
示例:
Object obj = new Object(); // obj 引用在栈,对象本体在堆 int[] arr = new int[10]; // 数组对象存储在堆 String s = "Hello"; // 字符串常量存储在堆的字符串常量池(JDK7+) -
特点:
- 通过
-Xmx和-Xms参数配置大小。 - 分为新生代(Eden + Survivor)和老年代。
- 通过
2. 虚拟机栈 (JVM Stack)
-
存储内容:方法调用的局部变量和方法操作栈。
-
示例:
public void method() { int a = 10; // 基本类型变量 a 存储在栈 String str = "World"; // 对象引用 str 存储在栈,对象本体在堆 Object obj = new Object(); // 引用在栈,对象在堆 } -
特点:
- 每个方法对应一个栈帧。
- 栈溢出会抛出
StackOverflowError。
3. 方法区 (Method Area)
-
存储内容:类结构、常量池、静态变量。
-
示例:
public class MyClass { static int count = 0; // 静态变量存储在方法区 final String NAME = "JVM"; // 常量 NAME 存储在运行时常量池(方法区) } -
特点:
- JDK8 之前称为“永久代”,JDK8+ 改为“元空间”(Metaspace),使用本地内存。
- 溢出会抛出
OutOfMemoryError: Metaspace。
4. 程序计数器 (PC Register)
-
存储内容:当前线程执行的字节码指令地址。
-
示例:
public void loop() { for (int i = 0; i < 10; i++) { // 程序计数器记录当前循环执行位置 } } -
特点:
- 唯一不会发生
OutOfMemoryError的区域。 - 多线程切换时用于恢复执行位置。
- 唯一不会发生
5. 本地方法栈 (Native Stack)
-
存储内容:Native 方法(如 JNI 调用)的执行信息。
-
示例:
public native void nativeMethod(); // 调用本地方法时使用此栈 -
特点:
- 与虚拟机栈类似,但服务于 Native 方法。
- 由 C/C++ 实现的方法调用链。
内存交互关系图示
+-------------------+ +-------------------+
| 虚拟机栈 (Stack) | | 堆 (Heap) |
| - 局部变量 |<----| - 对象实例 |
| - 对象引用 | | - 数组 |
+-------------------+ +-------------------+
|
v
+-------------------+
| 方法区 (Method Area)|
| - 类元信息 |
| - 运行时常量池 |
| - 静态变量 |
+-------------------+
总结规则
- 对象本体在堆:所有
new操作创建的对象实体。 - 引用在栈:对象引用、方法参数、局部变量存储在栈。
- 类元信息在方法区:类的结构、静态变量、常量池信息。
- 线程私有区域:栈、程序计数器、本地方法栈为线程私有,堆和方法区为线程共享。