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

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 首先需要加载 RuntimeAreaDemo 这个类。
    • 将类的 结构信息(如类名、父类、方法、字段等)、静态变量 staticMessage 及其值 "Hello from Method Area"方法字节码maincalculaterecursiveMethod<init> 构造方法的代码)都存储在这里。

2️⃣ 执行 RuntimeAreaDemo demo = new RuntimeAreaDemo(10);

  • Java 堆
    • new 关键字会在 中分配内存,创建一个 RuntimeAreaDemo 类的对象实例。
    • 这个对象包含了其所有 实例变量 的数据,这里就是 instanceId(此时被赋值为 10)。
    • 对象中还包含一些 元数据指针,指向 方法区 中该类的结构信息(用于知道这个对象是啥类型,有哪些方法)。
  • Java 虚拟机栈
    • main 方法的栈帧的局部变量表中,变量 demo 被赋予了一个值,这个值就是指向 中那个刚创建的对象的 引用(可以理解为地址)。对象本身在堆里,而指向它的引用在栈里。

3️⃣ 执行 int answer = demo.calculate(5);

  • Java 虚拟机栈
    • JVM 会为 calculate 方法创建一个 新的栈帧,并压入 main 线程的虚拟机栈(现在栈里有两个栈帧:maincalculate)。
    • calculate 栈帧的 局部变量表 包含:
      • this:指向调用该方法的对象(即 demo 引用的那个堆中的对象)。
      • operand:形参,值为 5
      • result:局部变量,用于存储计算结果。
    • 操作数栈 用于执行计算:取出 operand(5)this.instanceId(10)(通过 this 引用从堆中获取值)进行乘法运算,将结果 50 存入局部变量 result
  • 程序计数器
    • 切换到 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 虚拟机栈类似。
  • 直接内存
    • 我们的示例代码没有用到。但如果使用 java.nio 包下的 ByteBuffer.allocateDirect(1024) 来分配内存,这块内存就会绕过 Java 堆,直接在 直接内存 中分配。它虽然不属于运行时数据区,但同样受总内存限制,也可能导致 OutOfMemoryError

4.总结

在这里插入图片描述

代码部分
对应的运行时数据区域
说明
static String staticMessage方法区类级别的静态变量
new RuntimeAreaDemo(10)Java 堆对象实例本身
demo(变量)Java 虚拟机栈指向堆中对象的引用
operandresultcountJava 虚拟机栈方法内的局部变量
calculate()main() 等方法代码方法区已加载的类的方法字节码
递归调用 recursiveMethodJava 虚拟机栈大量栈帧导致 StackOverflowError
JVM 执行字节码程序计数器指向下一条该执行的指令
(未演示)调用本地方法本地方法栈管理本地方法调用的状态

通过这个案例,可以清晰地看到:

  • 栈里存放的是引用和局部变量
  • 堆里存放的是实实在在的对象
  • 而方法区存放的是类的 “蓝图” 和静态资源。

它们各司其职,共同协作来运行 Java 程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大数据与AI实验室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值