整体流程如下:

1. 类加载检查
分为两种情况类已经被加载过 和 未被加载过
1.1 未加载过
涉及三个阶段:加载、链接、初始化
加载:
- 通过类加载器将类文件(
.class)加载到内存中。
链接:
- 验证:检查类文件的正确性和安全性。
- 准备:为类的静态变量分配内存,并初始化默认值。
- 解析:将符号引用替换为直接引用。
初始化:
- 执行类的静态代码块和静态变量初始化。
以下为详细过程
(1) 加载(Loading)
- JVM 使用类加载器(ClassLoader)加载
.class文件,将类的字节码读取到内存中。 - 在内存中生成对应的 Class 对象,该对象存储类的元数据信息。
- 类加载器分类:
- 启动类加载器(Bootstrap ClassLoader):加载
Java核心库(如java.lang.String)。 - 扩展类加载器(ExtClassLoader):加载扩展库(
JDK提供的lib/ext下的库)。 - 应用类加载器(AppClassLoader):加载应用程序的类路径中的类。
- 自定义类加载器:用户自定义的类加载器,通常继承自
ClassLoader。
- 启动类加载器(Bootstrap ClassLoader):加载
(2) 链接(Linking)
将加载的类文件与 JVM 运行时环境进行连接,分为以下三个子步骤:
-
验证(Verification):
-
确保类文件格式正确,字节码安全,符合 JVM 的规范。
-
如果验证失败,抛出
VerifyError或ClassFormatError。
-
-
准备(Preparation):
-
为类的 静态变量 分配内存,并赋予默认值(如
int为0,float为0.0,引用为null)。 -
例如:
static int x = 10; // 此时 x 被赋值为 0,值 10 在初始化阶段赋予。
-
-
解析(Resolution):
- 将常量池中的符号引用替换为直接引用。
- 符号引用:是一种逻辑表示(如类名、字段名、方法签名)。
- 直接引用:是真实内存地址或指向方法区数据的指针。
(3) 初始化(Initialization)
-
JVM 执行类的初始化阶段,包括:
- 静态变量初始化:将静态变量赋予显式指定的值。
- 静态代码块执行:按顺序执行静态代码块。
-
初始化是在类的
<clinit>方法中实现的。该方法由编译器生成,包含所有静态变量的赋值和静态代码块。 -
例如:
class Example { static int x = 10; static { x += 5; } } // 初始化后,x 的值为 15
1.2 加载过
直接跳过此阶段
2. 内存分配
JVM 在堆中为对象分配内存。内存分配有两种方式:
- 指针碰撞(Bump-the-pointer):
- 如果堆内存是规整的,JVM 通过移动指针来分配内存。
- 空闲列表:
- 如果堆内存存在碎片,JVM 通过维护一个空闲列表来找到合适的内存块。
- 在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”
注意:是否规整取决于是否使用了垃圾收集器的压缩(如 G1、CMS)
3. 初始化对象的内存
- JVM 将对象分配的内存空间初始化为零值(包括引用为
null,数值型为 0)。 - 同时生成对象的对象头:
- Mark Word:存储对象的运行时数据(如哈希码、GC 状态)。
- Class Pointer:指向对象所属的类的元数据。
4. 执行构造方法
- 调用对象所属类的构造方法(
方法): - 首先初始化父类(通过调用
super())。 - 初始化实例变量,执行代码块和构造方法中定义的逻辑。
- 首先初始化父类(通过调用
- 如果类中没有显式定义构造方法,编译器会生成一个默认的无参构造方法。
5. 返回对象引用
- 对象创建完成后,JVM 返回对象的引用地址,将其存储在栈上的引用变量中,供程序使用。