详解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这个变量。这就完成了整个过程。

相关文章
|
1月前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
2月前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
54 17
|
1月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
2月前
|
存储 Java 数据管理
Java零基础-Java对象详解
【10月更文挑战第7天】Java零基础教学篇,手把手实践教学!
34 6
|
2月前
|
Oracle Java 关系型数据库
重新定义 Java 对象相等性
本文探讨了Java中的对象相等性问题,包括自反性、对称性、传递性和一致性等原则,并通过LaptopCharger类的例子展示了引用相等与内容相等的区别。文章还介绍了如何通过重写`equals`方法和使用`Comparator`接口来实现更复杂的相等度量,以满足特定的业务需求。
31 3
|
3月前
|
Java
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
本文介绍了Java中抽象类和抽象方法的使用,以及ArrayList的基本操作,包括添加、获取、删除元素和判断列表是否为空。
36 2
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
|
2月前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
|
2月前
|
XML Java Maven
在 Cucumber 测试中自动将 Cucumber 数据表映射到 Java 对象
在 Cucumber 测试中自动将 Cucumber 数据表映射到 Java 对象
69 7
|
2月前
|
存储 Java 数据管理
Java零基础-Java对象详解
【10月更文挑战第3天】Java零基础教学篇,手把手实践教学!
32 1
|
2月前
|
Java 数据安全/隐私保护
java类和对象
java类和对象
26 5