深入浅出理解 Java 虚拟机(JVM):Java 程序的运行引擎
Java 程序能够“一次编写,到处运行”,其基石是 Java 虚拟机(JVM)。它是一个虚拟的计算机,为 Java 字节码提供统一的运行环境。要理解 JVM 如何工作,就必须深入其内存结构。本文将详细解析 JVM 的核心组成部分,并重点阐明每个部分内部到底存放了哪些具体数据。
一、JVM 的核心组成与数据存储
JVM 的内存区域在逻辑上可以划分为几个核心部分,其中最重要的三个是:堆、Java 虚拟机栈 和 元空间。
- 堆:存放所有对象实例和数组。
- 栈:存放方法执行时的局部变量和部分过程结果。
- 元空间:存放类的元数据信息。
下面,我们来详细剖析这三个核心区域及其存储的数据。
二、堆的精细结构与数据存储
堆是 JVM 中最大的一块内存,是所有线程共享的。它的唯一使命就是存放对象本身。
-
堆的组成与数据
为了高效垃圾回收,堆被划分为新生代和老年代。-
新生代:新创建的对象在这里分配。新生代又分为:
- Eden 区:对象诞生的“伊甸园”。
- Survivor 区:有两个,S0 和 S1。
-
老年代:存放长期存活的对象。
-
-
堆里到底存了什么数据?
当我们执行MyObject obj = new MyObject();时,new MyObject()这个操作会在堆上创建一个对象实例。这个实例内部存储的数据包括:- 对象头:包含两类信息:
- 运行时元数据:如哈希码、GC 分代年龄、锁状态标志、线程持有的锁等。
- 类型指针:指向方法区中该对象的类元数据的指针,JVM 通过这个指针来确定这个对象是哪个类的实例。
- 实例数据:这是对象的有效信息,即我们在代码里定义的各种字段(成员变量)的内容。
- 例如,如果
MyObject类定义了int id和String name,那么id的值和name的引用(指向堆中的 String 对象)就存储在这里。
- 例如,如果
- 对齐填充:非必需部分,仅起占位符作用,确保对象起始地址是8字节的整数倍,以提高内存访问效率。
总结:堆里存的是通过
new关键字创建出来的对象实例的真实数据(字段值)以及对象本身的元信息(对象头)。 - 对象头:包含两类信息:
三、栈的功能与数据存储
栈是线程私有的,生命周期与线程相同。它描述的是 Java 方法执行的内存模型。
-
栈的组成:栈帧
每个方法在执行时,都会在栈中创建一个对应的栈帧。栈帧内部存储了该方法执行所需的所有数据,主要包括:- 局部变量表:
- 存储内容:这是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
- 存储的数据类型:存储编译期可知的各种基本数据类型(
boolean,byte,char,short,int,float,long,double)和对象引用。 - 关键点:局部变量表存放的是对象的引用(可以理解为地址),而对象本身存储在堆中。例如
MyObject obj = ...中的obj这个引用就放在局部变量表里,它指向堆中的实际对象。
- 操作数栈:
- 存储内容:这是一个后进先出的栈,用于进行计算方法过程中的中间结果。它是 JVM 执行引擎的工作区。
- 示例:计算
int a = 1 + 2;,字节码指令会先将 1 和 2 压入操作数栈,然后调用加法指令,从栈顶弹出两个值进行计算,再将结果 3 压回栈顶,最后将这个值存入局部变量表a对应的位置。
- 动态链接:指向方法区中该栈帧所属方法的引用,用于支持方法调用过程中的动态连接(多态的特性基于此实现)。
- 方法返回地址:存放该方法退出后,程序应该继续执行的下一条指令的地址。
- 局部变量表:
-
栈的功能:压栈与弹栈
- 方法调用时压栈:调用一个方法,就创建一个新栈帧并压入栈。
- 方法返回后弹栈:方法执行完毕,其栈帧被弹出,栈帧中所有数据随之销毁。
总结:栈里存的是方法执行过程中的“流水线数据”,包括方法的局部变量(基本类型值或对象引用)、计算的中间结果,以及方法调用的链接和返回信息。栈帧随方法调用而生,随方法结束而亡,生命周期很短。
四、方法区(元空间)的组成与数据存储
方法区(在 JDK 8+ 中由元空间实现)是各个线程共享的内存区域,它存储的是已被虚拟机加载的类型信息,可以看作是堆的“逻辑部分”,但物理上不连续。
方法区里到底存了什么数据?
- 类的类型信息:
- 类的完整有效名称(如
java.lang.Object)。 - 类的直接父类的完整有效名称。
- 类的访问修饰符(
public,abstract,final)。 - 类的直接接口的一个有序列表。
- 类的完整有效名称(如
- 类的字段信息(字段元数据):
- 字段名称、字段类型、字段的访问修饰符。
- 注意:字段的具体值(如果是非静态字段)存储在堆的对象实例中;如果是静态字段(
static变量),则其值就存储在方法区本身。
- 类的方法信息(方法元数据):
- 方法名称、方法的返回类型(或
void)。 - 方法参数的个数、类型、顺序。
- 方法的访问修饰符。
- 方法的字节码指令、异常表等。
- 注意:方法代码本身是字节码,存储在方法区;但方法执行时的局部变量和中间结果在栈中。
- 方法名称、方法的返回类型(或
- 运行时常量池:
- 这是类文件中“常量池表”的运行时常量池。存放编译期生成的各种字面量和符号引用。
- 字面量:如文本字符串(
"Hello")、被声明为final的常量值等。 - 符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
- 静态变量:即
static变量。静态变量是作为类信息的一部分存储在方法区中的。 - 即时编译器编译后的代码缓存:JIT 编译器将“热点代码”(频繁执行的方法/代码块)编译成本地机器代码后,也会存储在这里以提高性能。
总结:方法区(元空间)是类的“蓝图库”或“元信息仓库”,它存储的是类的结构信息,而不是对象的具体数据。它回答了“这个类长什么样子?”的问题。
总结与类比
为了更形象地理解,我们可以用一个比喻:
- 方法区(元空间):像一个建筑图纸库。里面存放着“房屋类”、“办公楼类”的蓝图(类信息),描述了每种建筑的结构(有哪些房间/字段,房间的用途/方法)。
- 堆:像一片巨大的建筑工地。根据图纸库里的蓝图,
new关键字就像施工队,在这里建起一栋栋真实的房屋对象和办公楼对象(对象实例)。这些建筑内部有实际的家具和人员(实例数据)。 - 栈:像工地上的项目经理的工作台。每个项目经理(线程)有自己的工作台。当他要盖一栋楼(调用一个方法)时,就在工作台上铺开一张临时图纸(栈帧),上面记着当前需要的材料清单(局部变量表)、正在进行的计算(操作数栈)、以及和图纸库的关联(动态链接)。楼盖好了,这张临时图纸就被收走销毁了。

浙公网安备 33010602011771号