JVM 如何创建一个对象?别再只说“new”了


类加载和对象创建过程

在 Java 世界中,“类加载”和“对象创建” 是两个看似基础却异常关键的概念。你可能已经写过无数个 new,但你是否真正了解在这背后 JVM 为你做了哪些“魔法操作”?

无论是面试官常问的“类是如何加载的?对象是怎么创建的?”,还是开发中遇到的各种奇妙现象,如类初始化顺序异常、对象未赋值问题,其实都离不开对这两个核心流程的深入理解。

本篇文章将带你从 JVM 的视角出发,全面剖析 Java 类加载过程与对象创建机制,让你对这两个过程形成系统认知,并掌握关键细节与底层原理。不仅能帮你面试答得漂亮,更能在实际开发中写出更稳定、更高效的代码。

类加载过程

系统加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析

1. 加载

  1. 通过全类名获取定义此类的二进制字节流。
  2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构。
  3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口。

执行结果:生成 java.lang.Class 对象。


2. 验证

  • 确保字节码符合 JVM 的安全规范,防止恶意代码运行。
  • 主要验证:文件格式(CLass 文件格式检查)、元数据(字节码语义检查)、字节码结构(程序语义检查)、符号引用(类的正确性检查)等。

保证 .class 文件的合法性和安全性。


3. 准备

  • 为类变量(static 修饰的字段)分配内存并设置默认值(非初始化值)。

例如:

  • 准备阶段只给类变量分配内存,不会给实例变量分配内存
  • 准备阶段正常只会赋零值。准备阶段后,a = 0
static int a = 10; // 此阶段 a=0

如果是加了final这种,会直接赋初始值,准备阶段后 a = 10

static final int a = 10;

4. 解析

  • 将常量池中的符号引用替换为直接引用
    • 符号引用:字符串形式,如“com/Example”
    • 直接引用:内存地址指向实际类或方法

5. 初始化

  • 执行类构造器初始化 <clinit>() 方法:

    <clinit>() 不是你写的代码,而是 JVM 自动生成的特殊方法,它的作用是:

    把类中所有静态变量的赋值语句静态代码块按顺序合并在一起,生成一个 <clinit>() 方法。

    只在类 初始化阶段 执行一次。

    • 合并所有静态变量的赋值动作和静态代码块
  • 按顺序执行:静态变量赋值 → 静态代码块

static int a = 10;
static {
    a = 20;
}

最终 a == 20

只有在初始化阶段才会真正执行程序编写的代码!


对象创建过程

Java 中对象的创建过程其实远比一个 new 看起来要复杂得多,它背后涉及 JVM 的类加载机制、内存分配、构造方法调用等多个步骤。

一、Java 创建对象的方式

常见的创建方式:

MyClass obj = new MyClass();              // 常见方式
Class clazz = Class.forName("MyClass");   // 反射方式
MyClass obj2 = clazz.newInstance();       // 反射创建
MyClass obj3 = obj.clone();               // clone 创建
MyClass obj4 = (MyClass) input.readObject(); // 反序列化

二、Java 中对象的创建过程(以 new 为例)

MyClass obj = new MyClass();

这个语句背后 JVM 做了 五个主要步骤

1. 类加载检查
  • 首先,JVM 检查 MyClass 类是否已经被加载、连接和初始化(即是否执行过类加载过程中的 7 个阶段)。
  • 如果没有,会触发类加载流程,包括执行 <clinit>() 方法。

2. 在堆内存中分配内存
  • JVM 会在 Java 堆中为对象分配内存空间,大小由类中的实例变量数量和类型决定。
  • 分配方式可能是“指针碰撞”或“空闲列表”策略。**选择那种方式取决于Java堆是否完整,而Java堆是否完整取决于所采用的垃圾收集器是否带有压缩整理功能
内存分配的两种方式
  • 指针碰撞:
    • 适用场合:堆内存规整(即没有内存碎片)的情况下。
    • 原理:用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界指针,只需要向着没用过的内存方向将该指针移动对象内存大小位置即可。
    • 使用该分配方式的 GC 收集器:Serial, ParNew
  • 空闲列表:
    • 适用场合:堆内存不规整的情况下。
    • 原理:虚拟机会维护一个列表,该列表中会记录哪些内存块是可用的,在分配的时候,找一块儿足够大的内存块儿来划分给对象实例,最后更新列表记录。
    • 使用该分配方式的 GC 收集器:CMS

选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的


3. 初始化零值(默认值)
  • 分配到的内存空间会被初始化为默认值(如 int 为 0,引用为 null,boolean 为 false)。
  • 这一步只是分配内存,还没有赋你写的初始值。

4. 设置对象头(元数据)

对象头会记录:

  • 对象的所属类元数据(Class 对象指针)
  • 对象的哈希码、GC 信息、锁状态
  • 用于支持同步、GC 和锁机制

5. 执行构造函数 <init>()
  • 执行你写的构造方法(构造器),这个方法叫做 <init>(),初始化对象字段的实际值。
public class MyClass {
    int x = 10;

    MyClass() {
        x = 20;
    }
}

执行顺序:

  1. 初始化为默认值(x = 0)
  2. 执行成员变量赋值(x = 10)
  3. 执行构造器赋值(x = 20)

参考

https://javaguide.cn/java/jvm/memory-area.html#%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%88%9B%E5%BB%BA

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值