当前位置: 首页 > 后端技术 > Java

详细完整的说说对象实例化过程

时间:2023-04-01 21:24:37 Java

把对象实例化过程说的详细、完整。对象实例化过程需要做什么工作?首先,Java是一种面向对象的语言。类是属于一个类的所有对象的抽象。一个对象的所有结构信息都在类中定义。因此,对象的创建需要根据类中定义的类型信息。是类对应的类二进制字节流,所以这里涉及到类的加载和初始化。其次,对象大多存放在堆内存中,这涉及到内存分配。此外,还有变量初始化零值、对象头设置、栈中创建的对象引用等,本文将详细分析对象的完整实例化过程。整体流程一整天的对象的整个实例化过程如下图所示:实例化的。publicclassDemo{publicstaticvoidmain(String[]args){DemoClassdc=newDemoClass();}}classDemoClass{privatestaticfinalinta=1;私有静态整数b=2;私人私人静态私人美食;d=4;私有接口;静态{c=3;}publicDemoClass(){e=5;}}类初始化检查这里我们使用new关键字来创建对象,Java中创建对象的方式有很多种,比如反射、克隆、序列化和反序列化等等。这些方法各不相同,但是经过编译器编译后,对应的是Java虚拟机中的一条新指令(这里的新指令不同于上面说的new关键字,是虚拟机级别的指令)。当Java虚拟机遇到一条新的指令时,首先会根据指令对应的参数去常量池中查找是否有该类对应的符号引用,判断该类是否已经被加载、解析,并初始化。即去方法区查看是否有类的类型信息。如果没有,则必须先执行类加载和初始化。如果该类已经加载并初始化,则继续后续操作。这里假设DemoClass类还没有被加载和初始化,即方法区中没有DemoClass类型信息,此时需要加载和初始化DemoClass类。类加载过程类加载过程大体分为7个步骤:加载、验证、准备、解析、初始化、使用、卸载。在这里,我们看一下前六个阶段。加载加载阶段主要做了三件事:根据类的完全限定名获取类的二进制字节流。在方法区将二进制字节流表示的静态存储结构转换为运行时数据结构。在内存中创建一个代表该类的Java.lang.Class对象,作为该类在方法区的各种数据的访问入口。这里具体是先根据package.DemoClass的全限定名定位到DemoClass.class二进制文件,然后将.class文件加载到内存中进行解析,将解析结果存入方法区,最后在Java中创建一个堆内存.lang.Class的对象用于访问方法区加载的类信息。验证验证阶段完成的任务主要是确保class文件中字节流所包含的信息符合Java虚拟机的规范。虽然很简单,但是Java虚拟机进行了很多复杂的验证工作。一般来说,可以分为四个方面:文件格式校验、元数据校验、字节码校验、符号引用校验。具体来说,这里是在虚拟机层面验证加载到内存中的DemoClass.class中存储的信息,确保DemoClass.class中存储的信息不会危及Java虚拟机的运行。准备准备阶段所做的工作是为类变量(即静态变量)分配内存并赋初值,通常是变量对应的数据类型的零值。但是在这个阶段,final修饰的变量,也就是常量,会在这个阶段被准确赋值。具体来说,在这个阶段,DemoClass中的a会被赋值为1,b和c都会被赋值为0。这个阶段解析的主要任务是将常量池中的符号引用替换为直接引用.在前面的初始化阶段,除了加载阶段自定义类加载器可以干预虚拟机的加载过程外,其他阶段完全由虚拟机主导,而在初始化阶段,类的执行根据程序员的意愿启动。初始化,这个阶段完成的主要工作是执行类的构造方法(),同时虚拟机保证在执行本类的类构造方法时,其父类的类构造方法有被正确执行。同时,由于类的初始化只进行一次。当多个线程并发初始化时,虚拟机可以保证多个线程中只有一个可以完成类的初始化工作,保证线程安全的工作。具体到DemoClass类,这个阶段b会被赋值2,c会被赋值3。分配内存当类加载过程完成,或者类本身之前已经加载过,下一步是虚拟机为新对象分配内存。类加载过程完成后,对象所需的内存空间就可以完全确定了。为对象分配内存空间,相当于从堆内存中分出一块合适的内存。分配内存的方式主要有两种:指针冲突和空闲列表。指针冲突:这种方法将堆内存分为空闲空间和已分配空间,并使用一个指针作为两者的分界线。在为新对象分配内存空间时,相当于将指针指向空闲空间可以看出,Java堆内存在这种分配方式下必须是有规律的,一侧是所有空闲空间,另一侧是已分配空间.Java指针冲突空闲列表:在虚拟机中维护一个列表,用于记录堆中哪块内存是空闲可用的。为新对象分配内存时,从链表中找到合适大小的可用内存块。分配完成后更新空闲列表。这样,堆内存的空闲空间和分配的空间就可以交错了。Java内存freelist从上面来看,选择使用指针碰撞还是freelist方式分配内存主要取决于Java堆内存是否规则,而Java堆内存是否规则取决于垃圾回收算法采用了,这就涉及到垃圾收集机制(可见知识是相通的,程序员只是活着学死!),GC后是否有压缩或整理动作等。同时,由于创建对象的动作非常频繁,可能有多个线程同时为对象申请内存空间分配。如果此时不采用某种同步机制,可能会导致以后有一个线程去修改指针。一个线程使用原始指针分配内存空间,因此衍生出两种解决方案:失败重试的CAS和TLAB方法。第一种方法很容易理解。多个线程使用CAS来更新指针。多线程下,只有一个线程可以更新指针,其他线程通过不断重试完成内存指针的重新移动。第二种方法是为每个线程预先分配一块内存空间。这个内存空间就是线程的本地缓冲区TLAB。这样,每次线程要分配内存时,先去TLAB获取。当TLAB中的内存空间不足时,采用同步机制。继续申请一块TLAB空间,减少了同步锁的申请次数。具体在这个阶段,在堆内存中为DemoClass对象开辟一块内存空间,即dc对象实例。初始化零值为对象分配内存后,虚拟机会将分配的内存初始化为零值,这样Java中对象的实例变量就可以不用初值使用,因为代码访问的是分配的零值由虚拟机为此内存。具体来说,Java虚拟机将上面分配的内存空间初始化为零值。这一步使得DemoClass中的d和e现在都赋值为0。设置对象头对象头就像我们人的身份证。它存储了一些标识对象的数据,即对象的一些元数据。我们先来看对象的构成。设置好对象头并初始化零值后,如何知道对象是哪个类实例,需要在方法区设置指向类型信息的指针,对象MarkWord中相关信息的设置就完成了在这个阶段。在实例对象初始化这一步中,虚拟机会按照我们程序员的意愿调用实例构造方法()来初始化对象。这一步会调用构造函数来完成实例对象的初始化。具体来说,DemoClass的d赋值为4,e赋值为5。创建一个引用,压栈,执行到这一步。创建的对象已经存在于堆内存中,但是我们知道在Java中使用对象是通过虚拟机栈中的引用获取对象属性并调用对象方法,所以这一步会创建对象的引用,压在虚拟机栈上,最后返回引用给我们使用。这里要说的是对象入栈,返回赋值给dc。此时,创建了一个对象。对象实例化的完整过程根据上面的讨论,我们来回顾一下对象实例化的整个过程: