详解java对象创建的过程

简介: java对象是怎么从代码变成一块内存空间的呢?只看代码层面我们只是使用了new关键字加上调用构造器,就生成了一个对象,然后我们就可以使用这个对象了,那么虚拟机在这当中究竟是怎么实现这个过程的呢,在这里我们一起学习下这个过程。

前言:java对象是怎么从代码变成一块内存空间的呢?只看代码层面我们只是使用了new关键字加上调用构造器,就生成了一个对象,然后我们就可以使用这个对象了,那么虚拟机在这当中究竟是怎么实现这个过程的呢,在这里我们一起学习下这个过程。


场景假设



1.java代码与字节码信息展示


假设有如下代码,Human是一个类,我们想要创建一个该类的对象供使用,下面我们围绕这段代码展开讨论:


pubic class TestCreateObject{
  public void test(){
    Human human = new Human();
  }
}


上方test方法对应的class字节码信息如下图(test方发表信息)


20210306195045601.png


我们可以清晰的看到这一行代码对应的虚拟机指令,这些字节码指令中真正是因为new关键字产生的指令有new指令、invokespecial指令。其他指令dup、aload、astore均于此无关。


2.虚拟机类加载的过程


回忆下类被虚拟机加载的过程,如下图,在说对象创建的时候回频繁使用到这流程,所以先列出来供随时观看。


20210306193302228.png


对象创建过程



1.虚拟机在碰到new指令时,会去检查运行时常量池中是否有Human类的符号引用,根据符号引号去找到这个类的class文件。


2.将Human对应的Class文件加载进入到虚拟机中,这个加载就是上方说话流程图中的加载,同时创建一个class对象。用以反射场景下使用,这里未用到该类的Class对象(java.lang.Class)。


3.当Human的class文件被加载进虚拟机后,虚拟机会对这个class文件的信息进行验证,这是“验证”阶段,比如验证魔数是否是0xCAFEBABE,验证主、次版本号,验证元数据,验证字节码指令、验证符号引用是否真实存在等。


4.当通过了上述的验证后虚拟机会为类变量赋初始值,这是“准备”阶段,这个阶段是专门为类级别的信息赋初始值的,Human中没有类变量则不涉及这个阶段。


5.然后虚拟机就开始去将该类中的符号引用翻译成直接引用,这是“解析”阶段,值得注意的是,解析阶段并不一定发生在这里,也可能是发生在上图中的“初始化”后,这里我们可以就当做在这里发生,以便于理解。将符号引用转化为直接引用后,在使用时就可以直接使用了。


6.到现在为止我们依然没有将该类完全加载完成,这个阶段需要去为类级别的变量赋值,这是“初始化”阶段,Human中没有类级别的变量、或者代码块,所以这块也是省了,注意刚刚说的“准备”阶段是赋初始值,这个阶段是赋程序里面设定的值,并不是一回事。


7.到现在为止Human的类型信息已经完全被加载进了内存中,这里才到了new指令的动作,前面一系列都是因为new指令触发的,并不是new真正要做的事情,虚拟机在堆中开辟一块内存用以存储Human对象,这里对象的分配,有两种方案,如果直接分配在了新生代,那么对象的分配一般使用“指针碰撞”的方式,如果被直接分配在了老年代则有可能使用"指针碰撞"也有可能使用“空闲列表”这主要取决于被分配的区域是否有对空间整理的能力。而且对象分配时虚拟机还会采取安全策略,一般使用CAS+失败重试的方式老保证线程安全,当然也有使用TLAB(本地线程分配缓冲)来保证线程安全的,这是一种为每个线程都预先分配一小块内存的方式。到现在为止一个没有任何数据的对象已经在虚拟机中产生了,一个对象在堆中分为三个部分:对象头、实例数据区、对其补充。


8.虚拟机在分配完内存后,会将除了对象头中以外的信息都进行初始化为0的操作,所以我们在日常开发中,实例变量不进行初始化是可以正常使用的,但是局部变量不实例化却不可以使用。


9.接下来,虚拟机需要为Human的对象设置必要的信息,首先是对象的头部信息,比如GC分代年龄、对象的哈希码等的设置,另外对象的头部中还存储了另外一个比较重要的信息“类型指针”,该指针指向了Human的元数据。


10.在以上所有操作都完成以后,其实一个不包含任何数据的对象就产生了,因为他的实例数据区域都是默认值0,刚刚一开始我们就说过,new关键字在被编译后变成了两条字节码指令,一个是new指令,一个是invokespecial指令,上面这些过程都是new指令完成的,既然new指令的活干完了,肯定就会轮到invokespecial指令了。该指令是虚拟机中5种方法调用指令之一,用以调用构造器、私有方法、父类方法等。这里自然是调用了Human的构造方法了


20210306211108352.png


从上方的标红部分可以看出,该指令的参数就是Human的构造器方法了,该部分运行后就会为对象中的实例数据区域进行赋值了。这样一个完整的对象就在堆中建立了起来,然后虚拟机将Human对象的地址给到占中human这个变量。这就完成了整个过程。

相关文章
|
5天前
|
安全 Java 编译器
java中类与对象回顾总结-2
java中类与对象回顾总结
14 3
|
5天前
|
Java 编译器
java中类与对象回顾总结-1
java中类与对象回顾总结
14 3
|
17天前
|
Java
【专栏】Java反射机制,该机制允许程序在运行时获取类信息、动态创建对象、调用方法和访问属性
【4月更文挑战第27天】本文探讨了Java反射机制,该机制允许程序在运行时获取类信息、动态创建对象、调用方法和访问属性。反射通过Class、Constructor、Method和Field类实现。文中列举了反射的应用场景,如动态创建对象、调用方法、访问属性和处理注解,并提供了相关实例代码演示。
|
1天前
|
Java 编译器
【Java开发指南 | 第一篇】类、对象基础概念及Java特征
【Java开发指南 | 第一篇】类、对象基础概念及Java特征
9 4
|
2天前
|
安全 Java 数据安全/隐私保护
Java一分钟之-Java反射机制:动态操作类与对象
【5月更文挑战第12天】本文介绍了Java反射机制的基本用法,包括获取Class对象、创建对象、访问字段和调用方法。同时,讨论了常见的问题和易错点,如忽略访问权限检查、未捕获异常以及性能损耗,并提供了相应的避免策略。理解反射的工作原理和合理使用有助于提升代码灵活性,但需注意其带来的安全风险和性能影响。
17 4
|
4天前
|
Java
【JAVA基础篇教学】第五篇:Java面向对象编程:类、对象、继承、多态
【JAVA基础篇教学】第五篇:Java面向对象编程:类、对象、继承、多态
|
4天前
|
缓存 Java 程序员
关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯
关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯
关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯
|
5天前
|
Java
从源码出发:JAVA中对象的比较
从源码出发:JAVA中对象的比较
19 3
|
6天前
|
Java
Java一分钟之-类与对象:面向对象编程入门
【5月更文挑战第8天】本文为Java面向对象编程的入门指南,介绍了类与对象的基础概念、常见问题及规避策略。文章通过代码示例展示了如何定义类,包括访问修饰符的适当使用、构造器的设计以及方法的封装。同时,讨论了对象创建与使用时可能遇到的内存泄漏、空指针异常和数据不一致等问题,并提供了相应的解决建议。学习OOP需注重理论与实践相结合,不断编写和优化代码。
27 1
|
12天前
|
SQL Java 数据库连接
15:MyBatis对象关系与映射结构-Java Spring
15:MyBatis对象关系与映射结构-Java Spring
30 4