对象的创建主要分为以下几个流程:
类加载检查
当我们新new一个对象时,首先去检查这个指令的参数是否能在常量池中定位到一个类的引用,并且检查这个引用代表的类是否已经被加载过,如果没有被加载过则执行相应的类加载过程(使用类加载器加载)。
内存分配
执行完上面的类加载步骤之后,就是为我们新new的对象分配内存,新的对象所需要的内存大小在加载完成之后便可确定,为对象分配空间相当于把一块已知大小的内存从堆中划分给新对象。
内存分配如何划分?会不会出现并发问题?
划分内存的方式指针碰撞和空闲列表。
指针碰撞(默认使用指针碰撞)是指如果堆中的内存绝对规整,所有用过的内存都放到一 边,没有用过的放到另外一边,中间放着一个指针用来当做一个分界线指示器,那所分配的内存就是把指示器往空闲空间移动指定对象大小的相等的距离即可。
空闲列表:如果堆中的内存并不是连续的规整的,已使用的内容和未使用的内容相互交错,就没有办法进行指针碰撞了,虚拟机必须维护一个列表用来指示队中的可用内存块,在分配内存的时候从列表中找一块够大的内存分配给对象实例,并且分配之后更新列表上的记录。
解决分配对象的并发问题有CAS和本地线程分配缓冲(TLAB)
CAS:虚拟机采用CAS加上失败重试的方式保证更新操作的原子性来分配对象的内存空间
本地线程分配缓冲(TLAB):每个线程会预先在堆中分配一块内存,然后线程中有新对象生成优先在线程划分的空间之内进行分配。通过-XX:+/-UseTLAB参数来设定虚拟机是否使用TLAB(JVM会默认开启-XX:+UseTLAB),-XX:TLABSize 指定TLAB大小。
初始化零值
内存分配完成之后虚拟机需要把分配到的内存空间都初始化为零值(不包括对象头),如果使用TLAB这一步骤可以提前至TLAB分配时进行。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
设置对象头
初始化零值之后,虚拟机要对对象进行必要的设置,比如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头Object Header之中。
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、 实例数据(Instance Data)和对齐填充(Padding)。 HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时 间戳等。对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
32位对象头
64位对象头
执行init方法
执行init方法,即对象按照程序员的意愿进行初始化。对应到语言层面上讲,就是为属性赋值(这与上面的赋零值不同,这是由程序员赋的值),和执行构造方法。