当虚拟机遇到一条字节码new指令时,虚拟机的步骤:
检查这个指令的参数是否能在常量池中定位大盘一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、 解析和初始化过。 如果没有, 那必须先执行相应的类加载过程
在类加载检查通过后, 接下来虚拟机将为新生对象分配内存,对象所需内存的大小在累加载完成之后可完全确定。
假设Java堆中内存是绝对规整的,那么可以将指针向空闲空间方向挪动一段对象大小的距离,这称为指针碰撞;但如果Java堆中的内存并不是规整的, 已被使用的内存和空闲的内存相互交错在一起 ,虚拟机会维护一个空闲列表,从列表中找到一块空间划分给对象实例。Java堆是否规整由垃圾处理器是是否带有空间压缩整理能力决定。
对象创建在虚拟机中是非常频繁的行为,修改指针位置存在线程安全问题。处理措施分类两种:分配内存空间做同步处理——CAS机制;在堆内为每个线程分配一块私有的内存区域,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB),只有缓冲使用完了,分配新的缓存区时需要同步锁定。虚拟机是否使用TLAB, 可以通过-XX: +/-UseTLAB参数来设定。
内存分配完成之后,虚拟机将分配到的内存空间(不包含对象头)都初始化为零值,如果使用了TLAB的话,这个工作可以提前至TLAB分配时进行。这部操作确保了对象的实例字段在Java代码中可以在不赋初值就直接使用。
设置对象头中的数据,例如这个对象是哪个类的实例、 如何才能找到类的元数据信息、 对象的哈希码、 对象的GC分代年龄等信息。
对象在堆内分为三部分:对象头、实例数据和对齐填充。
对象头包含两类信息:
第一类是用于存储对象自身的运行时数据, 如哈希码(HashCode) 、 GC分代年龄、 锁状态标志、 线程持有的锁、 偏向线程ID、 偏向时间戳等, 这部分数据的长度在32位和64位的虚拟机(未开启压缩指针) 中分别为32个比特和64个比特, 官方称它为“Mark Word”。对象头信息是与对象自身定义的数据无关的额外存储成本, Mark Word部分被设计成一个非固定的动态数据结构,它会根据对象的状态服用自己的存储空间。 具体的在并发部分进行了解。
另外一部分是类型指针, 即对象指向它的类型元数据的指针, Java虚拟机通过这个指针来确定该对象是哪个类的实例。此外, 如果对 象是一个Java数组, 那在对象头中还必须有一块用于记录数组长度的数据, 因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小, 但是如果数组的长度是不确定的, 将无法通过元数据中的信息推断出数组的大小
实例数据:在程序代码里面所有定义的各种类型的字段内容都必须记录。这部分的存储顺序受到虚拟机分配策略参数(-XX:FieldsAllocationStyle 参数)和Java源码中定义顺序的影响。HotSpot虚拟机默认的分配顺序为longs/doubles、 ints、 shorts/chars、 bytes/booleans、 oops(OrdinaryObject Pointers, OOPs) , 从以上默认的分配策略中可以看到, 相同宽度的字段总是被分配到一起存放, 在满足这个前提条件的情况下, 在父类中定义的变量会出现在子类之前。 如果HotSpot虚拟机的+XX: CompactFields参数值为true(默认就为true) , 那子类之中较窄的变量也允许插入父类变量的空隙之中, 以节省出一点点空间。
对齐填充 :仅仅起着占位符的作用。 由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍, 换句话说就是 任何对象的大小都必须是8字节的整数倍。 对象头部分已经被精心设计成正好是8字节的倍数(1倍或者2倍) , 因此, 如果对象实例数据部分没有对齐的话, 就需要通过对齐填充来补全。
分为两种方式:使用句柄和直接指针