当Java虚拟机遇到一条字节码new指令时:
①首先会进行类加载检查,即检查这个指令的参数是否能在常量池中定位到一个类的符号引用及这个符号引用代表的类是否被加载、解析和初始化过,如果没有就会执行类加载。
②类加载检查通过后,虚拟机将为新对象分配内存空间,即在Java堆中划分出一块对象的具体大小的内存块用于存放对象。
分配内存的方式分两种:
第一种是“指针碰撞”(Bump The Pointer)。使用该方式的前提是垃圾收集器带有空间压缩整理能力(Compact)(如Serial、ParNew),假设Java堆中的内存是绝对规整的,而使用空间压缩整理的内存空间即分为两块,一边是空闲内存,一边是使用过的内存,两者之间使用指针作为分界点的指示器,“指针碰撞”即是把这个指针向空闲空间方向挪动一段与对象大小相等的距离;
第二种是“空闲列表”(Free List)。假如Java堆中的内存不是规整的,例如使用的是CMS这种基于清理(Sweep)的垃圾收集器,导致已被使用的内存和空闲内存交错在一起,则必须维护一个列表,记录哪些内存块是可用的,在内存分配时基于该列表找到符合大小要求的内存块分配给对象实例存放,并更新列表。
由于虚拟机中对象的创建操作频繁,可能出现正在给对象A分配内存但指针还没来得及修改,对象B又使用了原来的指针分配内存的并发问题。
解决这个问题有两种方案:
第一种是对分配内存空间的操作进行同步处理,采用CAS加失败重试的方式保证更新操作的原子性;
第二种是本地线程分配缓存(Thread Local Allocation Buffer, TLAB),即在Java堆中给每个线程都预先分配一小块内存,只有当这块缓冲区用完了,才会使用同步锁定分配新的缓存区,而且分配到TLAB时可以顺便将分配到的内存空间都初始化为零值,
这样对象的实例可以不赋值就能直接使用
③实例化对象,Java虚拟机会将分配到的内存空间初始化为零值,这样对象的实例可以不赋值就能直接使用;并将类的元数据信息、对象的哈希码、GC分代年龄、是否启用偏向锁等信息放入对象头;
④执行<init>()构造函数,执行构造函数中的代码对对象进行初始化;
⑤将对象引用入栈。