1.对象的创建
Step1:检查
虚拟机在遇到一个new指令时,先检查这个指令的参数是否能在常量池中定位到一个类的符号引用。然后进行类加载检查。(涉及到类的加载机制)
Step2:为对象分配内存空间
在进行完类加载检查以后,开始为对象分配内存空间,大致有如下两种分配方式:
- 指针碰撞(要求堆中的内存绝对规整),Serial、ParNew等带Compact过程的收集器
- 空闲列表(适用于堆中的内存不规整),CMS这种基于Mark-Sweep算法的收集器
第一种:指针碰撞
当堆中的内存绝对规整的时候,将所有空闲的内存放在一边,所有使用过的内存放在另一边。中间使用一个指针来当作分界线的指示器,当创建一个新的对象时,指针向空闲的那一边移动大小相等的一段距离
第二种:空闲列表
当堆中的内存不规整的时候,虚拟机维护一张内存使用表,记录下来哪些内存是已经被使用过的,哪些还未被使用,在给新的对象分配空间时,会在列表中找一个大小足够的内存空间分配给这个新创建的对象。
总结:
选择哪种内存分配方式取决于Java堆是否规整,而Java堆是否规整又取决于虚拟机所采用的垃圾收集器是否带有压缩整理功能。
本地线程分配缓冲区(TLAB,全程ThreadLocalAllocBuffer):
对象的创建以及内存空间分配在Java堆中是很频繁的行为,中间可能会存在线程不安全的行为。
例如线程A创建了一个对象,正在分配内存空间,指针还没来得及向空闲的一边移动,线程B又创建了一个对象,来分配内存空间。这个过程中不能保证更新操作的原子性。
(什么叫做操作的原子性?我的理解如下:分子是由多个原子构成,就例如一个操作由多个步骤组成,那么只涉及到一步操作的就是原子操作。比如x++就不是一个原子操作,它要先取x的值,然后将x的值赋予出去,最后再加1,这中间涉及到多个步骤,所以这就不是原子操作。x=1是一个原子操作,因为它只有一步操作,那就是将1赋值给x)
这里的本地线程分配缓冲区其实就是一开始就在Java堆中给每个线程分配一个私有的内存空间。由这个线程所创建的对象,就在这个线程私有的分配缓冲区内进行内存空间分配。这样就不存在上述的不能保证更新操作的原子性这一问题。
只有分配缓冲区的内存空间用完以后,才需要同步锁定。虚拟机是否使用分配缓冲区,可以通过-XX:+/-UseTLAB参数来设定。
更新(2018-12-20):
TLAB占用的是Eden区的空间,TLAB初始分配的时候内存空间很小,默认情况下只占有Eden区的1%(不过也可以手动设置TLAB的大小),由于TLAB的空间一般不是很大,所以大对象总是要直接分配到堆上
参考博客:https://blog.csdn.net/xiaomingdetianxia/article/details/77688945
Step3:内存空间初始化为零
内存分配完成后,虚拟机要将分配的这块内存空间归零,如果使用了TLAB,这一步操作也可以提前到TLAB分配时进行。
作用:保证了对象的实例字段在Java代码中可以不赋初值就进行使用,程序能访问到这些字段的数据类型所对应的零值。
Step4:对对象进行设置
虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、怎么才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄信息等。
Step5:最后一步,执行init()方法
从虚拟机的角度来看,对象的创建已经完成了,但是从程序的角度来看,对象的创建才刚刚开始,因为这个时候只是执行了new指令,对象中的所有字段都还是0,还没有执行init()方法,init()方法是按照程序员的意愿,将对象初始化成一个真正可用的对象。