对象在内存中的创建顺序:
1.进行类加载检查
new一个对象时,首先将去检查这个指令的参数是否在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载,解析和初始化过。如果没有,那必须执行相应的类加载过程。
2.为新生对象划分内存
划分内存有以下两种方式:
1):指针碰撞
假设java堆中是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那分配内存就仅仅是把那个指针指向空闲空间的那一边挪动一段与对象大小相等的距离。
2):空闲列表
虚拟机维护一个内存列表,记录那些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。
在划分内存的时候,还需要考虑多并发的线程安全问题,解决这个问题有两种方案。
1):对分配内存空间的动作进行同步处理——实际上虚拟机采用的CAS配上失败重试的方式保证更新操作的原子性(乐观锁?)。
2):把内存分配的动作按照线程划分在不同空间中进行,即每个线程在java堆中预先分配一小块内存,称为“本地线程分配缓冲”
3.虚拟机对对象进行必要的设置
对象在内存中的布局
对象在内存中的存储布局可以分为3个区域:对象头,实例数据,和对齐填充
1.对象头
对象头包含两部分信息:
一部分用于存储对象自身的运行时数据,例如:哈希码、GC分代年龄、锁状态标识、线程持有的锁、偏向线程ID、偏向时间戳等
另一部分是类型指针,即对象指向它元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例。
2.实例数据
是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。这部分的存储顺序会受到“虚拟机分配策略参数”和字段在java源码中所定义的顺序的影响。Hotspot默认的分配策略是相同宽度的字段总是会被分配到一起。在满足这个的前提之下父类中定义的变量总在子类之前。如果CompactFields的参数值为true,那么子类中较窄的变量也可能会插入到父类变量的空虚之中。
3.对其填充
对其填充不是必然存在的,也没有什么特殊的含义,它仅仅是着占位符的作用。由于Hotspot的自动内存管理系统要求对象起始地址必须是8字节的整倍数。换句话说对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或2倍),因此对象实例数据部分没有对齐时,就需要填充来不全。