判断对象是否加载、链接、初始化
虚拟机遇到一条new指令,首先回检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析、初始化。如果没有,就执行相应类的加载过程。
为对象分配内存
如果内存是规整的:
“指针碰撞”:把内存分为已使用和未使用的两部分,中间用指针分割,分配内存的时候就把指针向空闲方向移动一段与对象内存大小相等的距离。
如果内存是不规整的:
“空闲列表”:用一个列表记录哪些内存块是可用的,在分配的时候从列表中找出一块足够大的空间分配给对象实例,并更新列表的记录。
选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
处理并发安全问题
在并发情况下可能会导致分配的内存被覆盖,可以使用两种方式解决
CAS + 失败重试:保证原子性
TLAB 把内存分配的动作按照线程划分在不同的空间中进行,即每个线程在 Java 堆中预先分配一小块内存,称为本地线程缓冲区,哪个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有当TLAB用完了,分配新的缓冲区时才需要同步锁定。
初始化分配到的空间
内存分配完成之后,虚拟机必须将分配到的内存空间(不包括对象头)都初始化为零值,这步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就可以直接使用。
设置对象的对象头
将对象的所属类、对象的hashcode、对象的GC信息、锁信息等数据存储在对象的对象头中。
执行init方法进行初始化
上面几步执行完之后,从虚拟机的角度来看,一个新的对象已经产生了。但是从 Java 程序的角度来看,对象创建才刚刚开始,对象需要的其他资源和状态资源也还没有按照预定的意图构造好。执行 () 方法,按照程序员的意愿对对象进行初始化,这样一个真正可用的对象才算完全被构造出来。
对象头
元数据:
哈希值、GC分代年龄、指向锁记录的指针、指向重量级锁的指针、偏向线程ID、偏向时间戳
类型指针:
类型指针就是对象指向他的类型元数据的指针,Java虚拟机通常通过这个指针来确定该对象是哪个类的实例
实例数据
它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)
存储顺序:
相同宽度的字段分配到一起存放:long/double、float、int、short、chars、byte/boolean、oops父类中定义的变量出现在子类之前如果+XX:CompactFields参数值为true,那子类之中较窄的变量也允许插入父类变量的空隙之中,以节省空间对齐填充
起占位符的作用
Java 堆中划分出一块内存来作为句柄池,reference 中存储的是对象的句柄地址,而句柄中包含了对象的实例数据与类型数据各自的地址。
优点:当移动对象(GC中狠普遍的行为)的时候,只需改变实例数据的指针,而不需要更改reference
缺点:访问速度低
直接访问(HotSpot使用的方式)reference中存储的直接就是对象地址,如果之访问对象本身的话,就不需要多一次间接访问的开销。