JVM 创建对象过程

Java 中对象的创建方式一般有两种:1)new xxx() 通过new关键字创建实例对象;2)通过反射创建对象。不管哪一种创建方式,JVM 底层的执行过程是一样的。
在这里插入图片描述

示例程序:

public class Person {//静态变量
    public  static  int staicVariabl=1;
    //成员变量
    public   int  objVariabl;//静态初始代码块
    static {
        staicVariabl=2;
    }//对象初始化代码块
    {
        objVariabl=88;
    }//构造方法
    public Person() {
        objVariabl=99;
    }public static void main(String[] args) {
        Person person=new Person();
    }}

1、检查类是否加载

当需要创建一个类的实例对象时,需要先判断该类是否被成功加载过,若没有加载,要先进行类的加载,如果加载过,会在堆区有一个类的 Class 对象,方法区会有类的相关元数据信息

类的加载过程:
(1)加载阶段:Person.class 文件经过加载后,会把类的相关信息加载到 JVM 内存中,解析出类的描述信息会保存到 Metaspace,同时在堆中生成一个代表这个类的 java.lang.Class 对象,作为这个类的各种数据的访问入口

在这里插入图片描述

(2)连接阶段:会对静态变量的值进行默认赋值,此时 Person 类的 staicVariabl 赋值为0

在这里插入图片描述
在这里插入图片描述

(3)初始化阶段:首先会对类的静态变量 staicVariabl 进行显示赋值(此时staicVariabl =1);然后收集类的静态代码块内容,生成一个类的 <clinit>() 方法并执行(此时staicVariabl =2)

在这里插入图片描述

2、创建对象

当我们执行上面代码中 main 方法中的的 Person person=new Person() 时,我们的对象就开始创建了。

(1)main 线程申请栈空间

首先 main 线程会在栈中申请一个自己的栈空间,然后调用 main 方法后会生成一个 main 方法的栈帧,然后执行 new Person() 。

(2)分配内存并实例变量初始化默认值

类加载成功后,JVM 会根据 Person 类元信息确定对象的大小了,然后 JVM 会在堆内存划分一块对象大小的内存空间出来分配给新生对象,同时对 Person 对象成员变量赋默认值,这样的话对象就可以在没有赋值情况下使用了,只不过访问对象的成员变量都是零值。
在这里插入图片描述
JVM 分配内存一般有两种方式:1.指针碰撞法 2.空闲列表

(1) 指针碰撞:前提堆中的内存空间比较规整,维护一个内存指针,然后通过移动指针的方式给对象分配内存空间。`JVM 默认使用指针碰撞分配内存。
在这里插入图片描述
(2)空闲列表:堆中的内存并不规整,比较凌乱,JVM 通过维护一个空闲列表的方式,为新生对象分配内存,这个空间列表中保存了所有空闲的内存,当对象需要分配内存时,从空闲列表中找到一块足够对象大小的内存,分给新生对象。
在这里插入图片描述
这种方式可能会产生一些碎片空间。因为并不是每次都凑巧能找到正好对象大小的空间,很多时候都是大于对象带下的,造成部分空间浪费。

(3)初始化对象

执行对象内部生成的 init() 方法(包括显示初始化成员变量值和 {} 初始化代码块两部分),最后执行对象构造方法(init() 方法执行完后 objVariabl=88,构造方法执行完后objVariabl=99)
在这里插入图片描述

(4)引用对象

对象实例化完毕后,再把栈中的 Person 对象引用地址指向 Person 对象在堆内存中的地址。
在这里插入图片描述

3、对象在内存的布局

对象创建完成后在内存中保存了保存的信息包括对象头、实例数据及对齐填充三类信息。

(1)对象头

在这里插入图片描述

对象头里主要:锁状态标志、持有锁的线程ID、,GC分代年龄、对象HashCode,类元信息地址、数组长度。

1) 锁状态标志:对象的加锁状态分为无锁、偏向锁、轻量级锁、重量级锁几种标记。
2)持有锁的线程: 持有当前对象锁定的线程ID。
3)GC分代年龄: 对象每经过一次GC还存活下来了,GC年龄就加1。
4)类元信息地址: 可通过对象找到类元信息,用于定位对象类型。
5) 数组长度: 当对象是数组类型的时候会记录数组的长度。

(2)实例数据

对象实例数据才是对象的自身真正的数据,主要包括自身的成员变量信息,同时还包括实现的接口、父类的成员变量信息。

(3)对齐填充

根据 JVM 规范对象申请的内存地址必须是8的倍数,换句话说对象在申请内存大小时候8字节的倍数,如果对象自身的信息大小没有达到申请的内存大小,那么这部分是对剩余部分进行填充。

Person 对象最终创建完成后内存中数据情况大概如下图:
在这里插入图片描述
示例:
在这里插入图片描述
内存图:
在这里插入图片描述

### JVM对象创建过程Java 程序通过 `new` 关键字来创建一个新的对象实例时,JVM 需要经历一系列复杂的操作才能使这个对象成为可使用的状态。具体来说: #### 1. 类型检查与加载 在执行任何其他动作之前,JVM 必须先确认该类型的定义已经被加载到了内存中。如果尚未加载,则会触发类加载器去查找并加载相应的 `.class` 文件。 #### 2. 内存分配 一旦类型信息被成功加载,下一步就是为新对象分配足够的连续空间用于存储其成员变量和其他必要的元数据。这部分工作通常由垃圾回收器管理的堆区负责完成[^1]。 ```java // 假设有一个简单的Person类 public class Person { private String name; } ``` 对于上述代码中的 `Person` 类,在创建新的 `Person` 实例时,JVM 将为其分配适当大小的空间以容纳 `name` 字段以及其他潜在的数据结构。 #### 3. 构造函数调用前的状态设置 此时虽然已经预留好了物理上的位置给即将诞生的新实体,但从逻辑层面看它仍然是未初始化的状态;即所有字段均保持默认值(如数值型为0,布尔型为false,引用类型则为空null),直到显式的赋值行为发生为止[^3]。 #### 4. 执行 `<init>` 方法 紧接着便是最重要的一步—-构造方法(`<init>`) 的实际调用了。这期间可能会涉及多个层次的操作: - 如果存在父类的话,那么子类构造器内部隐含着对其超类构造器的一次自动调用; - 用户自定义的一些初始化语句也会被执行; - 同样地还有静态/非静态初始块内的表达式求值等。 值得注意的是,只有当整个链路顺利完成之后,才会返回指向新建好且已配置完毕的对象引用给原始发起者。 ```java // 定义带参数构造器的例子 public class Employee extends Person{ public Employee(String n){ super(); // 调用父类无参构造器 this.name=n; // 设置员工姓名 } } ``` 在这个例子中,当我们尝试构建一个具体的 `Employee` 对象时,除了自身的初始化外还会依次向上追溯至最顶层基类 `Object` 来确保每一个环节都被妥善处理过。 #### 5. 返回对象引用 最后,经过前面几步精心打造出来的成品会被封装成一个有效的引用传递回最初发出请求的地方供后续使用。 综上所述,尽管表面上看起来只是简单的一个 `new` 操作符的应用,背后却隐藏着许多细致入微的工作流程保障了最终产物的质量和安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值