一、JVM结构
JVM 本质是一台 "虚拟的计算机",核心作用是运行 Java 字节码(.class 文件),屏蔽不同操作系统的底层差异,实现 "一次编写,到处运行"。

1、 类加载子系统
核心作用:
负责将磁盘上的.class字节码文件加载到 JVM 的运行时数据区中,是 JVM 执行代码的 "前置准备环节"。
具体流程 & 作用:
- 加载:通过类的全限定名(如
java.lang.String)找到.class文件,读取字节码并生成对应的Class对象; - 链接:验证字节码合法性(防止恶意代码)、为类变量分配内存并设置默认值、将符号引用替换为直接引用;
- 初始化:执行类的静态代码块(
static{})、为静态变量赋初始值,完成类的初始化。
关键特性:遵循 "双亲委派模型",保证类加载的安全性(避免核心类被篡改)。
2、运行时数据区
这是 JVM 最核心的内存区域,所有运行时的数据都存储在这里,分为线程私有 和线程共享两类。
| 结构 | 归属 | 核心作用 |
|---|---|---|
| 程序计数器 | 线程私有 | 记录当前线程执行的字节码指令地址(行号),保证线程切换后能恢复到正确执行位置;执行 native 方法时值为undefined。 |
| 虚拟机栈 | 线程私有 | 存储方法调用的 "栈帧"(每个方法执行时创建一个栈帧),包含局部变量表、操作数栈、方法出口等;方法调用入栈,执行完出栈,是线程安全的。 |
| 本地方法栈 | 线程私有 | 与虚拟机栈功能类似,但专门为 native 方法(如调用 C/C++ 代码)服务;HotSpot VM 中常与虚拟机栈合并。 |
| 堆(Heap) | 线程共享 | JVM 中最大的内存区域,唯一作用是存储对象实例和数组;是垃圾回收(GC)的核心区域,分为新生代(Eden、S0、S1)和老年代。 |
| 方法区(Method Area) | 线程共享 | 存储类的元数据(类名、字段、方法定义)、常量池、静态变量、JIT 编译后的代码等;JDK8 后改用 "元空间"(Metaspace),使用本地内存而非 JVM 堆内存。 |
| 运行时常量池 | 方法区子集 | 存放编译期生成的字面量(如字符串常量)和符号引用,运行时也可动态添加(如String.intern())。 |
3、执行引擎
核心作用:
负责执行运行时数据区中的字节码指令,是 JVM 的 "执行核心"。
核心组件:
- 解释器:逐行解释执行字节码,启动速度快,但执行效率低;
- 即时编译器(JIT):识别 "热点代码"(频繁执行的代码),将其编译为机器码并缓存,大幅提升执行效率;
- 垃圾回收器(GC):属于执行引擎的子集,负责回收堆 / 方法区中不再使用的对象,释放内存(如 Serial GC、G1 GC 等)。
4、本地方法接口
核心作用:
作为 Java 代码与本地代码(C/C++、汇编等)之间的 "桥梁",允许 JVM 调用本地方法,也支持本地方法反向调用 Java 方法。
应用场景:
当需要调用操作系统底层 API(如文件操作、硬件交互)时,通过 JNI 实现。
5、本地方法库
核心作用:
存放本地方法的具体实现代码(如 C/C++ 编写的.so/.dll库文件),供 JNI 调用,是本地方法的 "实际执行载体"。
二、JDK、JRE、JVM、JNI
1、JVM(Java 虚拟机)
- 核心定义 :是一台 "虚拟的计算机",是 Java 程序运行的核心执行引擎,负责解释 / 编译执行 Java 字节码(.class 文件),屏蔽 Windows、Linux、Mac 等不同操作系统的底层差异,实现 "一次编写,到处运行"。
- 关键特点:单独的 JVM 无法运行 Java 程序(需要依赖类库),它是纯执行层,不包含任何开发 / 运行所需的类库或工具。
- 通俗比喻:相当于汽车的 "发动机",负责驱动程序运行,但发动机需要燃油(类库)才能工作。
2、JRE(Java 运行时环境)
- 核心定义 :是运行 Java 程序的最小必备环境 ,包含两部分核心内容:
- 基础:JVM(执行引擎);
- 核心类库:Java 标准库的核心部分(如
java.lang、java.util、java.io等),以及必要的配置文件。
- 关键特点:只能运行已编译好的 Java 程序(.class 文件),无法开发 / 编译程序。
- 通俗比喻:相当于 "发动机(JVM)+ 燃油(核心类库)",有了它就能跑 Java 程序,但不能造程序。
3、JDK(Java 开发工具包)
- 核心定义 :是开发、编译、调试 Java 程序的完整工具包 ,包含:
- 基础:完整的 JRE(运行时环境);
- 开发工具:javac(Java 编译器,将.java 文件编译为.class)、javadoc(文档生成工具)、jdb(调试器)、jar(打包工具)等。
- 关键特点:程序员开发 Java 程序的全套装备,包含运行和开发的所有能力。
- 通俗比喻:相当于 "运行环境(JRE)+ 工具箱(开发 / 编译 / 调试工具)",既能造程序,也能跑程序。
4、JNI(Java 本地接口)
- 核心定义 :不是独立的 "软件包",而是一套编程接口 / 规范,属于 JVM 的核心组成部分。它的作用是搭建 Java 代码和本地代码(C/C++、汇编等)之间的通信桥梁,允许 Java 程序调用操作系统底层的本地方法,也支持本地代码反向调用 Java 方法。
- 关键特点:是 JVM 的 "内置功能",而非独立层级,服务于 Java 与底层系统的交互。
- 通俗比喻:相当于 "发动机(JVM)上的一个接口 / 通道",让 Java 程序能和操作系统的底层代码(如文件操作、硬件交互)对话。
5、四者完整关系
1. 核心层级(包含关系):JDK ⊃ JRE ⊃ JVM
bash
JDK(Java开发工具包)
├── JRE(Java运行时环境)
│ ├── JVM(Java虚拟机,核心执行层)
│ └── 核心类库(java.lang、java.util等,运行程序的基础)
└── 开发工具集(javac、javadoc、jdb、jar等,开发/编译/调试用)
2、 JNI 与三者的关联关系(非包含,是 JVM 的内置能力)
- JNI 是 JVM 的 "内置接口":JNI 属于 JVM 的核心结构,不是独立的层级,只要有 JVM 就有 JNI;
- JNI 服务于 JRE/JDK:
- JRE 中的核心类库(如
java.io文件操作、java.net网络通信)底层会通过 JNI 调用操作系统的本地代码; - 开发人员用 JDK 编写程序时,若需调用本地代码(如优化性能、访问硬件),可基于 JNI 规范编写本地方法,最终由 JVM 执行。
- JRE 中的核心类库(如
三、java堆
Java堆是和Java应用程序关系最为密切的内存空间,几乎所有的对象都存放在堆中。并且Java堆是完全自动化管理的,通过垃圾回收机制,垃圾对象会被自动清理,而不需要显式地释放。
根据垃圾回收机制的不同,Java堆有可能拥有不同的结构。最为常见的一种构成是将整个Java堆分为新生代和老年代。新生代包含Eden+Survivor区,survivor区里面分为from和to区,内存回收时,如果用的是复制算法,从from复制到to(我们也教s0,s1),当经过一次或者多次GC之后,存活下来的对象会被移动到老年区其中,新生代有可能分为eden区、s0区、s1区,s0 和s1也被称为from和to区域,它们是两块大小相等、可以互换角色的内存空间。

在绝大多数情况下,对象首先分配在eden区,在一次新生代回收后,如果对象还存活,则会进入s0或者s1,之后,每经过一次新生代回收, 对象如果存活,它的年龄就会加1。当对象的年龄达到一定条件后,就会被认为是老年对象,从而进入老年代。
栈、堆、方法区的关系
声明的局部变量存放在栈中,而方法中的对象实例创建放在堆中,剩下的类与方法的实现在方法区。

四、出入java栈
Java栈是一块线程私有的内存空间。如果说,Java堆和程序数据密切相关,那么Java栈就是和线程执行密切相关的。线程执行的基本行为是函数调用,每次函数调用的数据都是通过Java栈传递的。
Java栈只支持出栈 和入栈两种操作。
栈帧
在Java栈中保存的主要内容为栈帧。每一次函数调用, 都会有一个对应的栈帧
被压入Java栈,每一个函数调用结束,都会有一个栈帧被弹出Java栈。

五、局部变量表
局部变量表是 JVM中每个栈帧的核心组成部分之一,专门用于存储方法执行过程中的局部数据,是理解 JVM 方法调用和内存分配的关键概念。
1、定位
局部变量表是栈帧的 "本地数据存储区",仅服务于当前方法的执行,是线程私有、完全隔离的内存区域。
2、存储内容:
方法的形参 :包括普通参数、this引用(非 static 方法),参数会优先占用局部变量表的前几个位置。
方法内定义的局部变量:包括基本数据类型(8 种)、引用类型(对象引用 / 数组引用)、返回值地址(极少场景)。
局部变量表不存储对象实例本身(对象实例在堆中),仅存储引用类型的 "指针" 或基本数据类型的直接值。
3、结构与组成:变量槽
局部变量表的最小存储单元是变量槽。
1、Slot 的大小:JVM 规范中定义 1 个 Slot 占用 4 个字节(32 位),可存储:
基本数据类型:byte、short、char、int、float、boolean(都会占用 1 个 Slot,boolean 实际按 int 存储)。
引用类型:所有对象 / 数组引用。
2、占用规则:
long、double 类型占用2 个连续的 槽位。
其他类型均占用 1 个 槽位
3、参数存储顺序:
方法形参按声明顺序依次占用槽,非 static 方法的第 0 个 槽位固定存储this引用。
4、特性
1、生命周期
局部变量表随栈帧创建而初始化,随栈帧销毁而释放。
作用域仅限当前方法,方法执行完毕后,局部变量表中的数据会立即失效,无法被其他方法访问。
2、不可动态扩展
局部变量表的大小在编译期就已确定,写入到方法的字节码中,运行时无法修改。
方法中局部变量的数量 / 类型一旦确定,运行时不会因为逻辑变化(如循环创建变量)而改变局部变量表的大小。
3、与垃圾回收无关
局部变量表存储的是方法内临时数据,生命周期与栈帧绑定,栈帧销毁后数据直接释放,无需 GC 参与。
只有堆中的对象实例才会被 GC 回收,局部变量表中的引用仅作为指向堆对象的 "指针"。
5、影响局部变量表大小的因素
1、方法参数的数量和类型
参数越多、包含 long/double 类型越多,占用槽位越多。
2、方法内局部变量的数量和类型
定义的局部变量越多(尤其是 long/double),Slot 占用越多。
3、方法类型
static 方法无this引用,比同参数的非 static 方法少占用 1 个槽位。
6、局部变量的回收
6.1局部变量本身的"回收"(非GC)
局部变量本身存储在虚拟机栈的局部变量表,其回收和垃圾回收无关,是 JVM 方法调用机制的自动行为。
回收本质:
不是 "垃圾回收",而是栈帧销毁时的内存释放。
回收时机:
方法正常执行完毕或异常终止,栈帧从虚拟机栈中出栈。
局部变量表随栈帧一起被销毁,其中存储的内容占用的槽位空间被 JVM 回收,用于后续方法调用的栈帧分配。
核心特点:
即时性:方法结束后立即释放,无需等待任何 GC 线程。
不可控性:程序员无法通过代码手动触发(如调用 System.gc () 也无效),完全由 JVM 的方法调用栈机制管理。
线程隔离:局部变量是线程私有,回收仅影响当前线程的栈空间,无线程安全问题。
6.2 局部变量引用的堆对象的回收(GC)
局部变量不会存储对象实例本身(对象实例在堆中),仅存储对象的引用指针。堆对象的回收才是 GC 的核心,局部变量的状态直接影响堆对象的可达性。
核心规则:
堆对象是否被回收,取决于是否存在可达的引用链------ 只要局部变量还持有堆对象的引用,该对象就会被标记为 "存活",不会被 GC 回收;反之则可能被回收。
注意点:
GC 的 "延迟性":即使满足上述场景,堆对象只是被标记为 "可回收",实际回收需等待 GC 线程执行,而非立即释放。
System.gc () 仅为 "建议":调用该方法只是提醒 JVM 执行 GC,JVM 可忽略,无法保证堆对象立即回收。
六、操作数栈
操作数栈是 JVM中每个栈的核心组成部分,是方法执行过程中完成算术运算、方法调用的 "临时计算区",也是理解 JVM 字节码执行逻辑的关键。
1、本质
操作数栈是一个后进先出 的栈结构(无索引,只能操作栈顶元素),专门用于存储方法执行过程中需要计算的临时数据、指令的操作数和运算结果。
2、核心作用
1、支撑字节码指令的算术运算。
2、支撑方法调用 / 返回。
3、作为局部变量表和计算逻辑之间的 "数据中转站"。
3、所属关系
每个栈帧对应一个独立的操作数栈,随栈帧创建而初始化,随栈帧销毁而释放,线程私有,完全隔离。
4、核心结构与存储规则
1、存储单元
以 "数据项" 为基本单位,无局部变量表的 "Slot" 概念,但有明确的类型和深度规则:
01、可存储数据类型:基本数据类型(8 种)、引用类型(对象 / 数组引用)、方法返回地址。
02、类型转换规则:byte/short/char/boolean 类型入栈前会先转换为 int 类型。
2、栈深度限制
01、操作数栈的最大深度 在编译期就已确定,写入到方法的字节码中,运行时不可动态扩展。
02、若运行时操作数栈的实际深度超出编译期设定的最大值,会抛出StackOverflowError。
3、64 位数据处理
01、long、double 类型占用2 个连续的栈深度位置。
02、其他类型(int、float、引用等)仅占用 1 个栈深度位置。
5、基础操作指令分类
| 指令类型 | 示例指令 | 作用 |
|---|---|---|
| 入栈指令 | bipush(常量入栈)、iload_1(局部变量表数据入栈) |
将常量、局部变量表中的数据压入操作数栈 |
| 运算指令 | iadd(int 相加)、lmul(long 相乘)、if_icmpgt(int 比较) |
弹出栈顶数据进行运算,结果压回栈(或直接用于判断) |
| 出栈指令 | istore_2(栈顶数据存入局部变量表) |
将栈顶数据弹出,写入局部变量表指定位置 |
| 方法调用 / 返回 | invokevirtual(调用实例方法)、ireturn(返回 int 值) |
方法调用时传递参数(压栈),返回时将结果压栈 |
| 字节码指令 | 操作数栈状态(栈底→栈顶) | 指令说明 |
|---|---|---|
iload_1 |
[x] | 将局部变量表 Slot1 的参数 x 压入栈 |
bipush 5 |
[x, 5] | 将常量 5 压入栈 |
iadd |
[x+5] | 弹出 x 和 5,相加后将结果压栈 |
istore_2 |
[] | 弹出 x+5,存入局部变量表 Slot2(即变量 y) |
iload_2 |
[y] | 将局部变量表 Slot2 的 y 压入栈 |
bipush 2 |
[y, 2] | 将常量 2 压入栈 |
imul |
[y*2] | 弹出 y 和 2,相乘后将结果压栈 |
ireturn |
[] | 弹出栈顶的 y*2,作为方法返回值 |
操作数栈和局部变量表是栈帧中最核心的两个组件,二者频繁交互完成方法执行,关系如下:

- 数据流向 1(局部变量表 → 操作数栈) :通过
xload_n指令(如iload_1、aload_2),将局部变量表中指定位置的数据压入操作数栈,为计算做准备; - 数据流向 2(操作数栈 → 局部变量表) :通过
xstore_n指令(如istore_2、astore_3),将操作数栈顶的计算结果弹出,存入局部变量表指定位置(即赋值给局部变量); - 核心定位对比 :
- 局部变量表:是 "数据存储区",存储方法参数、局部变量,可通过索引随机访问;
- 操作数栈:是 "数据计算区",仅支持栈顶操作,专门完成运算和指令交互。
6、特性
1、线程私有:每个线程的每个方法调用都有独立的操作数栈,无并发安全问题。
2、无 GC 参与:操作数栈存储的是临时计算数据,随栈帧销毁而释放,无需垃圾回收。
3、类型严格匹配 :JVM 会严格校验操作数栈中数据的类型与指令要求是否匹配(如iadd只能处理 int 类型),不匹配则抛出VerifyError或ClassCastException。
7、异常场景
1、StackOverflowError:操作数栈深度超出编译期设定的最大值(多因方法递归过深、局部变量 / 运算过多导致栈帧整体过大)。
2、NullPointerException :若操作数栈中存储的引用为 null,却执行调用实例方法 / 访问字段的指令(如invokevirtual)。
3、VerifyError :字节码校验阶段发现操作数栈类型不匹配(如用ladd处理 int 数据)。