深入浅出理解 Java 虚拟机(JVM):Java 程序的运行引擎

Java 程序能够“一次编写,到处运行”,其基石是 Java 虚拟机(JVM)。它是一个虚拟的计算机,为 Java 字节码提供统一的运行环境。要理解 JVM 如何工作,就必须深入其内存结构。本文将详细解析 JVM 的核心组成部分,并重点阐明每个部分内部到底存放了哪些具体数据

一、JVM 的核心组成与数据存储

JVM 的内存区域在逻辑上可以划分为几个核心部分,其中最重要的三个是:堆、Java 虚拟机栈 和 元空间

  • :存放所有对象实例数组
  • :存放方法执行时的局部变量部分过程结果
  • 元空间:存放类的元数据信息。

下面,我们来详细剖析这三个核心区域及其存储的数据。

二、堆的精细结构与数据存储

堆是 JVM 中最大的一块内存,是所有线程共享的。它的唯一使命就是存放对象本身

  1. 堆的组成与数据
    为了高效垃圾回收,堆被划分为新生代老年代

    • 新生代:新创建的对象在这里分配。新生代又分为:

      • Eden 区:对象诞生的“伊甸园”。
      • Survivor 区:有两个,S0 和 S1。
    • 老年代:存放长期存活的对象。

  2. 堆里到底存了什么数据?
    当我们执行 MyObject obj = new MyObject(); 时,new MyObject() 这个操作会在堆上创建一个对象实例。这个实例内部存储的数据包括:

    • 对象头:包含两类信息:
      • 运行时元数据:如哈希码、GC 分代年龄、锁状态标志、线程持有的锁等。
      • 类型指针:指向方法区中该对象的类元数据的指针,JVM 通过这个指针来确定这个对象是哪个类的实例。
    • 实例数据:这是对象的有效信息,即我们在代码里定义的各种字段(成员变量)的内容。
      • 例如,如果 MyObject 类定义了 int idString name,那么 id 的值和 name 的引用(指向堆中的 String 对象)就存储在这里。
    • 对齐填充:非必需部分,仅起占位符作用,确保对象起始地址是8字节的整数倍,以提高内存访问效率。

    总结:堆里存的是通过 new 关键字创建出来的对象实例的真实数据(字段值)以及对象本身的元信息(对象头)。

三、栈的功能与数据存储

栈是线程私有的,生命周期与线程相同。它描述的是 Java 方法执行的内存模型。

  1. 栈的组成:栈帧
    每个方法在执行时,都会在栈中创建一个对应的栈帧。栈帧内部存储了该方法执行所需的所有数据,主要包括:

    • 局部变量表
      • 存储内容:这是一组变量值存储空间,用于存放方法参数方法内部定义的局部变量
      • 存储的数据类型:存储编译期可知的各种基本数据类型(boolean, byte, char, short, int, float, long, double)和对象引用
      • 关键点:局部变量表存放的是对象的引用(可以理解为地址),而对象本身存储在中。例如 MyObject obj = ... 中的 obj 这个引用就放在局部变量表里,它指向堆中的实际对象。
    • 操作数栈
      • 存储内容:这是一个后进先出的栈,用于进行计算方法过程中的中间结果。它是 JVM 执行引擎的工作区。
      • 示例:计算 int a = 1 + 2;,字节码指令会先将 1 和 2 压入操作数栈,然后调用加法指令,从栈顶弹出两个值进行计算,再将结果 3 压回栈顶,最后将这个值存入局部变量表 a 对应的位置。
    • 动态链接:指向方法区中该栈帧所属方法的引用,用于支持方法调用过程中的动态连接(多态的特性基于此实现)。
    • 方法返回地址:存放该方法退出后,程序应该继续执行的下一条指令的地址。
  2. 栈的功能:压栈与弹栈

    • 方法调用时压栈:调用一个方法,就创建一个新栈帧并压入栈。
    • 方法返回后弹栈:方法执行完毕,其栈帧被弹出,栈帧中所有数据随之销毁。

    总结:栈里存的是方法执行过程中的“流水线数据”,包括方法的局部变量(基本类型值或对象引用)、计算的中间结果,以及方法调用的链接和返回信息。栈帧随方法调用而生,随方法结束而亡,生命周期很短。

四、方法区(元空间)的组成与数据存储

方法区(在 JDK 8+ 中由元空间实现)是各个线程共享的内存区域,它存储的是已被虚拟机加载的类型信息,可以看作是堆的“逻辑部分”,但物理上不连续。

方法区里到底存了什么数据?

  • 类的类型信息
    • 类的完整有效名称(如 java.lang.Object)。
    • 类的直接父类的完整有效名称。
    • 类的访问修饰符(public, abstract, final)。
    • 类的直接接口的一个有序列表。
  • 类的字段信息(字段元数据):
    • 字段名称、字段类型、字段的访问修饰符。
    • 注意:字段的具体值(如果是非静态字段)存储在的对象实例中;如果是静态字段(static 变量),则其值就存储在方法区本身。
  • 类的方法信息(方法元数据):
    • 方法名称、方法的返回类型(或 void)。
    • 方法参数的个数、类型、顺序。
    • 方法的访问修饰符。
    • 方法的字节码指令、异常表等。
    • 注意:方法代码本身是字节码,存储在方法区;但方法执行时的局部变量和中间结果在中。
  • 运行时常量池
    • 这是类文件中“常量池表”的运行时常量池。存放编译期生成的各种字面量符号引用
    • 字面量:如文本字符串("Hello")、被声明为 final 的常量值等。
    • 符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
  • 静态变量:即 static 变量。静态变量是作为类信息的一部分存储在方法区中的。
  • 即时编译器编译后的代码缓存:JIT 编译器将“热点代码”(频繁执行的方法/代码块)编译成本地机器代码后,也会存储在这里以提高性能。

总结:方法区(元空间)是类的“蓝图库”或“元信息仓库”,它存储的是类的结构信息,而不是对象的具体数据。它回答了“这个类长什么样子?”的问题。

总结与类比

为了更形象地理解,我们可以用一个比喻:

  • 方法区(元空间):像一个建筑图纸库。里面存放着“房屋类”、“办公楼类”的蓝图(类信息),描述了每种建筑的结构(有哪些房间/字段,房间的用途/方法)。
  • :像一片巨大的建筑工地。根据图纸库里的蓝图,new 关键字就像施工队,在这里建起一栋栋真实的房屋对象办公楼对象(对象实例)。这些建筑内部有实际的家具和人员(实例数据)。
  • :像工地上的项目经理的工作台。每个项目经理(线程)有自己的工作台。当他要盖一栋楼(调用一个方法)时,就在工作台上铺开一张临时图纸(栈帧),上面记着当前需要的材料清单(局部变量表)、正在进行的计算(操作数栈)、以及和图纸库的关联(动态链接)。楼盖好了,这张临时图纸就被收走销毁了。
posted @ 2025-11-22 15:09  谁来着?  阅读(5)  评论(0)    收藏  举报