Java 运行时数据区域(二):案例讲解

下面通过一个具体的代码示例来生动地说明各个运行时数据区域是如何协同工作的。
1.示例代码
public class RuntimeAreaDemo {
// 静态变量 -> 方法区
private static String staticMessage = "Hello from Method Area";
// 实例变量 -> 随着对象在堆中分配
private int instanceId;
public RuntimeAreaDemo(int id) {
this.instanceId = id;
}
// Java方法 -> 方法区
public int calculate(int operand) {
// 局部变量 operand, result -> 虚拟机栈的栈帧中的局部变量表
int result = operand * instanceId;
System.out.println(staticMessage);
return result;
}
// 一个会递归的方法,用于演示栈溢出
public void recursiveMethod(int count) {
if (count <= 0) {
return;
}
// 每次递归调用都会在虚拟机栈创建一个新的栈帧
recursiveMethod(count - 1);
}
public static void main(String[] args) {
// 局部变量 args, demo -> 虚拟机栈 (main方法的栈帧)
// new RuntimeAreaDemo(10) -> 对象在堆中创建
RuntimeAreaDemo demo = new RuntimeAreaDemo(10);
// 方法调用:计算
int answer = demo.calculate(5); // 步骤1
System.out.println("Answer: " + answer);
// 方法调用:递归 (故意引发栈溢出)
demo.recursiveMethod(10000); // 步骤2
}
}
现在,让我们一步步跟踪这个程序的执行,看看各个区域是如何被使用的。
2.程序执行过程与各区域的作用
1️⃣ 程序启动,main 线程开始执行
- 程序计数器:
- 立即开始工作,记录
main线程当前执行的字节码指令的地址。比如,它先指向new RuntimeAreaDemo(10)对应的指令,执行完后,再指向invokevirtual #5 <RuntimeAreaDemo.calculate>(调用 calculate 方法)的指令。
- 立即开始工作,记录
- Java 虚拟机栈:
- JVM 为
main线程创建了一个 栈帧,并压入虚拟机栈。 - 这个栈帧的 局部变量表 中会存储
args(命令行参数)和demo(一个引用)。
- JVM 为
- 方法区:
- JVM 首先需要加载
RuntimeAreaDemo这个类。 - 将类的 结构信息(如类名、父类、方法、字段等)、静态变量
staticMessage及其值"Hello from Method Area"、方法字节码(main、calculate、recursiveMethod、<init>构造方法的代码)都存储在这里。
- JVM 首先需要加载
2️⃣ 执行 RuntimeAreaDemo demo = new RuntimeAreaDemo(10);
- Java 堆:
new关键字会在 堆 中分配内存,创建一个RuntimeAreaDemo类的对象实例。- 这个对象包含了其所有 实例变量 的数据,这里就是
instanceId(此时被赋值为 10)。 - 对象中还包含一些 元数据指针,指向 方法区 中该类的结构信息(用于知道这个对象是啥类型,有哪些方法)。
- Java 虚拟机栈:
- 在
main方法的栈帧的局部变量表中,变量demo被赋予了一个值,这个值就是指向 堆 中那个刚创建的对象的 引用(可以理解为地址)。对象本身在堆里,而指向它的引用在栈里。
- 在
3️⃣ 执行 int answer = demo.calculate(5);
- Java 虚拟机栈:
- JVM 会为
calculate方法创建一个 新的栈帧,并压入main线程的虚拟机栈(现在栈里有两个栈帧:main、calculate)。 calculate栈帧的 局部变量表 包含:this:指向调用该方法的对象(即demo引用的那个堆中的对象)。operand:形参,值为5。result:局部变量,用于存储计算结果。
- 操作数栈 用于执行计算:取出
operand(5)和this.instanceId(10)(通过this引用从堆中获取值)进行乘法运算,将结果50存入局部变量result。
- JVM 会为
- 程序计数器:
- 切换到
calculate方法对应的字节码指令流,指引执行引擎一条条执行。
- 切换到
- 方法区:
- 执行
System.out.println(staticMessage);时,需要从 方法区 获取静态变量staticMessage的值。
- 执行
- Java 堆:
- 通过
this引用,从堆中的对象实例里获取instanceId的值10。
- 通过
4️⃣ 执行 demo.recursiveMethod(10000);
- Java 虚拟机栈:
- 这是一个递归调用。第一次调用时,创建第一个
recursiveMethod的栈帧。 - 然后递归调用自己,创建第二个栈帧,第三个栈帧… 每个栈帧都有自己的局部变量
count。 - 由于递归深度太大(10000 层),虚拟机栈的内存空间被耗尽。
- 结果:JVM 抛出
StackOverflowError错误。这正是因为线程私有的 虚拟机栈 容量无法容纳这么多栈帧。
- 这是一个递归调用。第一次调用时,创建第一个
3.其他区域的说明
- 本地方法栈:
- 如果我们的代码中调用了用 C/C++ 编写的 本地方法(例如
Object.clone()、Thread.start()的底层实现),那么这些本地方法的调用信息就会在本地方法栈中管理,与 Java 虚拟机栈类似。
- 如果我们的代码中调用了用 C/C++ 编写的 本地方法(例如
- 直接内存:
- 我们的示例代码没有用到。但如果使用
java.nio包下的ByteBuffer.allocateDirect(1024)来分配内存,这块内存就会绕过 Java 堆,直接在 直接内存 中分配。它虽然不属于运行时数据区,但同样受总内存限制,也可能导致OutOfMemoryError。
- 我们的示例代码没有用到。但如果使用
4.总结

|
|
|
|
|---|---|---|
static String staticMessage | 方法区 | 类级别的静态变量 |
new RuntimeAreaDemo(10) | Java 堆 | 对象实例本身 |
demo(变量) | Java 虚拟机栈 | 指向堆中对象的引用 |
operand、result、count | Java 虚拟机栈 | 方法内的局部变量 |
calculate()、main() 等方法代码 | 方法区 | 已加载的类的方法字节码 |
递归调用 recursiveMethod | Java 虚拟机栈 | 大量栈帧导致 StackOverflowError |
| JVM 执行字节码 | 程序计数器 | 指向下一条该执行的指令 |
| (未演示)调用本地方法 | 本地方法栈 | 管理本地方法调用的状态 |
通过这个案例,可以清晰地看到:
- 栈里存放的是引用和局部变量
- 堆里存放的是实实在在的对象
- 而方法区存放的是类的 “蓝图” 和静态资源。
它们各司其职,共同协作来运行 Java 程序。
Java运行时数据区案例解析

被折叠的 条评论
为什么被折叠?



