1.类加载检查
当虚拟机收到一条new指令时,首先将检查当前new的类是否在常量池被加载过(在常量池找到需要new的类的符号,检查其是否被初始化过)。如果没有,则执行相应的类加载过程;如果有则直接准备为新的对象分配内存。
2.为新生对象分配内存
对象所需内存大小在类加载完成后就已确定,分配内存的过程等同于将一块确定大小的内存从java堆划分出来。分配方式有指针碰撞和空闲列表两种,选择哪种分配方式由Java堆是否规整决定;Java堆是否规整又由选择的GC收集器是否带有压缩整理功能决定。
2.1 两种分配内存的方式
2.1.1 指针碰撞
- 适用于内存空间规整的情况。
- 想象一个内存空间:左侧是已使用的内存区域,右侧是空闲内存区域,中间是指针来作为分界点的指示器。当我们分配内存时,只需要指针向右移动与对象所需内存相同大小的距离即可,所以称为“指针碰撞”
- 代表GC收集器:Serial、ParNew
2.1.2 空闲列表
- 适用于内存空间不规整的情况。
- 当空闲内存区域和已使用内存区域相互交错时,虚拟机就需要维护一个列表,用来记录空闲内存块。当需要分配内存的时候,则需要找到一块足够大的内存区域分给对象实例,并更新表中的记录。
- 代表GC收集器:CMS
2.2 内存分配的并发问题解决
对象创建在虚拟机中属于频繁操作,这就涉及到了并发操作(当给对象A分配内存并且还没有分配完毕时,给对象B分配相同的的内存区域)。解决方案主要包括两种:CAS和TLAB
2.2.1 CAS+失败重试
CAS(compare and swap),每次假设不会发生冲突而去进行分配,冲突失败则自旋挂起,重试直到成功为止,减少用户态和内核态切换所需时间和资源占用。CAS+失败重试保证更新操作的原子性。
2.2.2 TLAB
本地线程分配缓冲(Thread Local Allocation Buffer, TLAB),是否使用TLAB通过-XX:+/-UseTLAB参数设置,java层面与之对应的是ThreadLocal类的实现
分配过程:
- 将所有内存分配的操作划分到多个不同线程中进行,预先从Eden区给每个线程分配一小块内存(默认Eden区的1%),称为TLAB
- 哪个线程需要分配内存,先在那一个线程的TLAB上进行分配
- 当线程的TLAB用完了或者TLAB剩余内存不足以存放对象时,向Eden区重新申请TLAB,再次尝试分配
- 如果放不下,则采用CAS的方式进行同步锁定,在Eden区尝试分配
- 如果还是放不下,在Eden区执行Young GC(minor GC),在Eden区尝试分配
- 如果还是放不下,则将对象分配至老年代
3.初始化零值
-
在内存分配完毕后,需要对分配后的内存空间初始化为零值(如果使用TLAB,则提前至分配TLAB时进行)。这一步保证了Java对象无需赋值即可直接使用,程序能访问到这些字段的数据类型的零值。
-
抽象数据类型默认初始化为null,基本数据类型为0,布尔为false...
4.设置对象必要参数
- 对对象的对象头(Object Header)进行初始化,包括类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,这些信息存放在对象头中,某些参数,比如是否启用偏向锁等,对象头会有不同的设置方式。
5.执行对象构造(init)方法
对于虚拟机来说,在执行init方法前,对象的创建已经结束了。但对于java程序来说,创建对象的过程还没有结束(在执行init方法之前,所有字段值均为零)。执行new指令后,会接着执行init方法,把对象按照程序员的想法进行初始化,才算完成了一个对象的创建。
网友评论