
一个 Java 程序对应一个 JVM,而一个 JVM 对应一套数据区!


PC Register):这只是一个很小的内存空间,保存着下一条执行的指令的地址。native 方法,则这个值是未定义的。JVM Stack):存放了方法调用的关系。JVM 栈中每一个元素,称为栈帧(Stack Frame),每个方法在调用时都会创建一个新的栈帧。比如:局部变量,当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。JVM 会抛出如 StackOverflowError 和 OutOfMemoryError,都和该区域有关。3. 本地方法栈(Native Method Stack):用于支持 Native 方法的调用。
Native 方法的局部变量。C/C++ 实现的。JVM 实现中(例如HotSpot),本地方法栈和虚拟机栈是一起的。4. 堆(Heap):JVM 中最大的内存区域,保存使用 new 创建的对象实例。
Eden、两个Survivor) 和 老年代Java 对象在内存中分为三部分:Header):存储锁状态、GC 分代年龄、哈希码等Instance Data):对象的字段值Padding):补齐内存对齐5. 方法区 (Method Area):存储类的元数据信息(对象实例存放在堆中,方法区只是存放类的元数据)
Java8 以后,方法区 -> 元空间,从原来的 "堆内存" 中移到 "本地内存" 中。+-------------------------------+
| 操作系统分配的总内存(物理RAM)|
+-------------------------------+
| |
| +------------------+
| |
+--------v------+ +--------------v------------------+
| JVM 管理区域 | | JVM 外部使用但不直接管理(本地内存)|
| | | |
| - 堆(heap) | | - 元空间(Metaspace) |
| - 栈(stack) | | - DirectBuffer / JNI 内存 |
| - 程序计数器 | | - JIT 编译缓存 / 线程内部结构 |
+--------------+ +----------------------------------+
JVM 执行流程大概如下所示:

口诀:编译成 class,加载做五步,解释+JIT,运行靠内存,回收靠 GC。
类加载是 JVM 把 .class 文件(字节码)加载进内存,并在内存中生成一个 Class 对象的 全过程,包括下面三个主要阶段:
Class 对象。<clinit> 静态代码块和静态变量赋值。JVM 核心部件,负责执行字节码:
JVM 内存模型,运行时管理所有数据:
区域 | 作用描述 |
|---|---|
方法区(Method Area) | 存储类的信息(类元数据)、常量、静态变量等 |
堆(Heap) | 存储对象实例,是垃圾回收的主要区域 |
虚拟机栈(Stack) | 每个线程私有,存储方法调用的信息(帧栈)、局部变量等 |
本地方法栈 | 为执行 native 方法准备 |
程序计数器(PC) | 每个线程私有,记录当前线程所执行的字节码行号 |
JVM 可以调用本地语言(如 C、C++)写的函数,依赖 JNI(Java Native Interface)。
Class Loading一个类在什么时候触发 "加载" 呢❓❓❓ ① 构造某个类的实例时 ② 调用类的静态方法/静态成员时 ③ 使用子类时,也会触发父类的加载
对于一个类来说,它的生命周期是这样子的:

其中前五步是固定的顺序,也是类加载的过程!
JVM 根据类的 "全限定名",委托给类加载器去加载该类,并遵循 双亲委派模型.class 文件中读取字节流JVM 将字节流解析为 Class 对象,放入方法区中(这不等于立即解析所有内容)此时:
2. 验证(Verification)
.class 文件,不是乱写的二进制)JVM 崩溃)3. 准备(Preparation)
static int a = 10; 只分配内存,初始化为默认值 0,而还没到赋值为 10 的阶段。4. 解析(Resolution)
5. 初始化(Initialization)
.class 文件的结构.class 文件是一个 以字节为单位的严格二进制格式 的结构体,包含类的所有元信息、方法字节码、常量池等。如下所示:
ClassFile {
u4 magic; // 魔数,标识文件的类型
u2 minor_version; // 次版本号
u2 major_version; // 主版本号
u2 constant_pool_count; // 常量池数量
cp_info constant_pool[]; // 常量池,包括字符串、类名、方法名、字段名等,最核心的部分
u2 access_flags; // 访问标志(public, abstract, final等)
u2 this_class; // 当前类的索引(指向常量池)
u2 super_class; // 父类索引(指向常量池)
u2 interfaces_count; // 实现接口数量
u2 interfaces[]; // 接口表
u2 fields_count; // 字段数量
field_info fields[]; // 字段表
u2 methods_count; // 方法数量
method_info methods[]; // 方法表
u2 attributes_count; // 属性数量
attribute_info attributes[]; // 属性表(如源码信息、注解、行号表等)
}类加载器(ClassLoader)是类加载机制的核心,本质上就是一个 "负责读取类的字节流并交给 JVM 转换为 Class 对象" 的工具。
JVM 提供了 三种主要的类加载器:
类加载器 | 作用 |
|---|---|
Bootstrap ClassLoader | 启动类加载器:加载 java 核心类库 |
Extension ClassLoader | 扩展类加载器:加载拓展目录下的类(jdk 自带但不是标准约定的库) |
Application ClassLoader | 应用类加载器:加载你写的代码和第三方库类 |
在类加载的加载阶段,JVM 会根据类的全限定名,通过类加载器加载该类。类加载器在加载类时默认遵循 "双亲委派模型",即优先让父类加载器尝试加载类,只有在父加载器无法加载时,才由当前加载器尝试加载。
原则:从下往上委托,然后先父后子,从上往下加载。

过程如下所示:
class 文件,如果加载不到,则一层一层往下尝试是否能进行加载。这样子做是为了保证:
java.lang.Object、String 这类核心类一定由 启动类加载器 加载,避免用户自定义的类覆盖核心类,提升安全性。java.lang.String,但加载器是从上往下的,此时会先加载父类中的 java.lang.String,而不会加载到用户伪造的那个版本!原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。