一、类加载检查。
在实例化一个对象的时候,JVM 首先会去检查目标对象是否已经被加载并初始化了。如果没有,JVM 需要立刻去加载目标类,然后调用目标类的构造器完成初始化。然后初始化的过程,主要是对目标类里面的静态变量、成员变量、静态代码块进行初始化
二、分配内存。
当目标类被初始化以后,就可以从常量池里面找到对应的类元信息,并且目标对象的大小在类加载之后就已经确定了,所以这个时候就需要为新创建的对象,根据目标对象的大小在堆内存里面分配内存空间。内存分配的方式一般有两种,一种指针碰撞,另一种是空闲列表,JVM 会根据 Java 堆内存是否规整来决定内存分配方式。
-
指针碰撞:如果Java堆的内存是规整的,即所有用过的内存放在一边,而空闲的的放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离,这样便完成分配内存工作。
-
空闲列表:如果Java堆的内存不是规整的,则需要由虚拟机维护一个列表来记录那些内存是可用的,这样在分配的时候可以从列表中查询到足够大的内存分配给对象,并在分配后更新列表记录
划分内存时还需要考虑一个问题-并发,也有两种方式: CAS同步处理,或者本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。
-
CAS同步处理: 对分配内存空间的动作进行同步处理(采用 CAS + 失败重试来保障更新操作的原子性)
-
本地线程分配缓冲 TLAB:把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。哪个线程要分配内存,就在哪个线程的 TLAB 上分配。只有 TLAB 用完并分配新的 TLAB 时,才需要同步锁。通过-XX:+/-UserTLAB参数来设定虚拟机是否使用TLAB
三、初始化零值。
接下来,JVM 会把目标对象里面的普通成员变量初始化为零值,比如 int 类型初始化为0,对象类型初始化为 null,(类变量在类加载的准备阶段就已经初始化过了)。这一步操作主要是保证对象里面的实例字段,不用初始化就可以直接使用,也就是程序能够获得这些字段对应数据类型的零值 。
四、设置对象头。
JVM 还需要对目标对象的对象头做一些设置,比如对象所属的类元信息、对象的 GC 分代年龄、hashcode、锁标记等等
五、执行init方法。
初始化成员变量的值、执行构造块、最后执行目标对象的构造方法,完成对象的创建。