对象的建立
类初始化
当JVM遇到一条new的指令时,首先去检查这个指令是否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经加载,解析和初始化过。如果没有,那么必须先执行类的初始化。
划分空间
接下来就是要在堆中划分出一块空间,这块空间的大小由类去确定,在类加载以后,一个对象的大小已经是确定的了。对于这个划分,可能存在两种情况:
1. 堆的空间是规整的,已用过的部分在一边,未用过的部分在另外一边,有一个指针指向未使用部分的头,每次移动这个指针就可以了,这种方法称为“指针碰撞”。
2. 堆的空间是零散的,使用和未使用的部分交叉排列,这时候需要一个维护列表,记录哪些内存是可用的,在分配的时候找到一块足够大的内存进行分配,这种方法称为”空闲列表”。
实际中采用哪种分配方式是由虚拟机采用的垃圾收集算法决定的,主要取决于垃圾收集器是否带有压缩整理功能。因此,在使用serial,parNew等带有compact过程的收集器时,采用的是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,采用空闲列表。
如何保证对象创建的线程安全性?
- 采用CAS+失败重试保证更新操作的原子性
- 把内存分配动作按照线程划分在不同空间中进行。在堆中为每个线程分配一小块空间,称为本地线程分配缓冲(TLAB)。线程分配内存时,在自己的TLAB分配,当TLAB使用完时,使用同步锁。
赋零值
内存分配完成后,虚拟机把分配到的内存空间初始化为零值。如果使用TLAB,这一过程在TLAB中进行,这一步保证了Java实例字段在Java代码中不同赋值都可以使用。
设置对象头
虚拟机要对对象进行必要的设置,例如对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息,这些信息都保存在对象头中。
“init”指令
在上面的工作完成以后,对于虚拟机来说,对象已经创建完成,但从Java对象的角度来说,它还没有进行初始化,init方法还没有执行,所有字段都是零值。init方法可以理解为对象的构造方法。