对象创建
Java对象:数组,class对象,普通对象
普通对象的创建过程:
==> 收到new指令。
==> 检查这个指令的参数是否能再常量池中定位到一个类的引用。
==> 检查这个符号引用代表的类是否已被加载、解析、初始化过。(如果没有,则进行类加载)
==> 虚拟机为新生对象分配内存。(对象所需内存大小在类加载完成后可完全确定)
如何分配内存?
如果内存是规整的,指针碰撞,移动指针就可以了。(Serial,ParNew等带Compact过程的收集器)
如果内存不规整的,空闲列表,维护一个列表,记录哪些内存块可用。(CMS这种基于Mark-Sweep的)
如何处理并发?
进行同步处理,CAS+失败重试的方式保证更新操作的原子性
按线程划分 TLAB(本地内存缓冲区),只需要在TLAB用完并分配新的TLAB时需要同步锁定
==> 虚拟机将分配的内存空间全部初始化为零值(不包括对象头)
==> 虚拟机对对象头进行设置。
这个对象是哪个类的实例、如何找到类的元数据信息、对象的hash码、对象的gc分代年龄。
是否启用偏向锁等
==> 执行<init>方法,通过构造函数按程序逻辑进行初始化。
==> 对象创建完成。
内存布局
-
对象头
-
MarkWord,用于存储对象自身的运行时数据
- 哈希码
- GC分代年龄
- 锁状态标志
- 线程持有的锁
- 偏向线程id
- 偏向时间戳
未锁定的状态下:
对象哈希码 GC分代年龄 锁标志位 固定为0 25bit 4bit 2bit 1bit 其他状态下:(轻量级锁定、重量级锁定、GC标记、可偏向)
存储内容 标志位 状态 对象哈希码、对象分代年龄 01 未锁定 指向锁记录的指针 00 轻量级锁定 指向重量级锁的指针 10 膨胀 空,不需要记录信息 11 GC标记 偏向线程id、偏向时间戳、对象分代年龄 01 可偏向 -
类型指针,即指向它的类元数据的指针(通过元数据可以确定对象的大小)
-
数组长度,如果对象是数组(通过元数据无法确定数组大小,所以需要记录数组长度)
-
-
实例数据
对象真正存储的有效信息,包括父类继承的和子类定义的
存储顺序受虚拟机分配策略参数和源码中定义顺序的影响
默认分配策略:(相同宽度的字段总是分配到一起)
- longs/doubles
- ints
- shorts/chars
- bytes/booleans
- oops(Ordinary Object Pointers)
在父类中定义的变量会出现在子类之前,如果CompactFields参数值为true(默认为true),那么子类之中较窄的变量也可能会插到父类变量的空隙之中
-
对齐填充
HotSpot自动内存管理系统要求对象起始地址必须是8字节的整数倍,即对象的大小必须是8的整数倍。因此如果对象的实例数据部分没有对齐时,就需要通过对齐填充来补全
访问定位
-
句柄访问(存储对象实例数据的指针和到对象类型数据的指针)
好处:稳定的reference地址,对象被移动时只会改变句柄中的对象实例数据指针
-
直接指针(需要对象实例数据存储到对象类型数据的指针,HotSpot VM)
好处:速度更快,节省了一次指针定位的时间开销