虚拟机是如何创建Java对象的

虚拟机在执行字节码指令时遇到new指令,则开始创建对象。

类检查

首先,虚拟机会检查该指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用所代表的类是否已经被加载、解析、初始化过,如果没有,则先进行类加载

分配内存

接着,虚拟机将为对象分配内存,对象所需要的内存大小在类加载的过程中可以被确定下来,分配内存有两种方式,根据堆内存是否规整来选择其中一种方式分配内存,内存是否规整又由垃圾回收器所采用的垃圾回收算法是否带有整理(compact)功能来决定,使用SerialParNew等回收器器时,内存是规整的,使用CMS这种基于标记-清理的垃圾回收算法的回收器时,内存是不规整的

  • 指针碰撞
  • 空闲列表

在分配内存时,除了需要考虑如何划分空间,还要考虑线程安全问题,因为对象的创建是非常频繁的,在修改指针的位置时,可能出现正在给对象A分配内存时,还没有将指针修改,对象B就修改了指针的位置,针对这种情况,一般有两种解决方案

  • 对分配内存空间的动作进行同步处理,虚拟机内部是采用CAS + 失败重试的方式来保证更新操作的原子性

  • 把内存分配的动作按照线程划分在不同的内存空间中进行,每个线程在Java堆中先划分一小块内存,这块内存称为TLAB(ThreadLocal Allocation Buffer),对象优先在TLAB分配,直到TLAB被用完,分配新的TLAB给线程时才进行同步操作,通过参数-XX:+/-UseTLAB来告诉虚拟机是否使用TLAB

指针碰撞

堆内存规整的情况下,一般会选择指针碰撞的方式来分配内存。规整的内存表示堆中已使用的内存和未使用的内存被分为两个隔离的部分,一部分是已经被分配过的,一部分是未被分配的,他们之间会有一个指针,指向两块内存的分界点,当给新的对象分配内存,只需要将该指针向未被分配内存一侧移动对象大小相应的距离即可,这样就完成了对象内存分配

空闲列表

如果堆内存并不规整,即未使用的内存和已使用的内存交错存放,此时就会使用空闲列表的方式来为对象分配新的内存,虚拟机会维护一份空闲内存列表,存放了所有未被使用的内存区域,给新的对象分配内存时只需要在该列表中寻找一块足够大的内存区域即可

初始化零值

内存分配完成后,就要给实例属性赋初始值,如,int -> 0,boolean -> false,如果开启了TLAB,这步操作也可以在TLAB中完成,这一步不会对对象头中的属性做设置

设置对象的基本信息

这一步就是对对象头进行设置,例如该对象是哪个类的实例,如何找到类的元数据信息,hashcode,对象GC分代年龄,是否使用偏向锁等

这一步完成之后,在虚拟机中已经能算作是创建了一个新的对象。

执行对象的构造方法<init>()

编译器在遇到new指令时会同时生成invokespecial指令,根据是否有invokespecial指令来确定是否要执行对象的<init>()方法

该方法执行完成后,一个对象就创建完成了