HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。下图为普通对象和数组实例的存储结构:
1.1 对象头
HotSpot虚拟机对象头分为:(Mark Word)存储对象运行时数据,Klass(类型指针)、数组长度(只有数组对象有)
1.1.1 对象自身运行时数据
数组运行时数据包含:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
锁状态标识
锁状态
当一个对象刚开始new出来时,该对象是无锁状态。此时偏向锁位为0,锁标志位01,如果有线程上锁:指的就是把markword的线程ID改为自己线程ID的过程;如果有线程竞争:撤销偏向锁,升级轻量级锁,线程在自己的线程栈生成LockRecord,用CAS操作将markword设置为指向自己这个线程的LockRecord的指针,设置成功者得到锁;如果竞争加剧竞争加剧:有线程超过10次自旋,
-XX:PreBlockSpin,或者自旋线程数超过CPU核树的一半,1.6之后,加入自适应自旋adapative self spinning,JVM自己控制;升级重量级锁: 向操作系统升级资源,等待操作系统的调度,然后再映射会用户空间;
- 分代年龄
分代年龄占4位,即最大值为15,所以新生代中的对象在eden区和survior区进行15次转移,当达到最大值时,再转移到老年代
1.1.2 类型指针
对象头的另外一部分是klass类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例.
1.1.3 数组长度(只有数组对象有)
如果对象是一个数组, 那在对象头中还必须有一块数据用于记录数组长度.
1.2 实例数据
实例数据部分就是存储对象的有效信息的,即在代码中定义的各种字段的内容,无论是从父类继承下来的,还是在子类中定义的,都需要存储。不过其顺序并不一定就是代码中定义的顺序,HostSpot虚拟机的策略是根据类型宽度来存储,例如double和long类型存储在一块儿,short和char存储在一块儿,在满足这个条件的前提下,父类中定义的变量会出现在子类定义的变量之前,即假设父类定义了一个double a = 3.0,子类定义了一个long b = 2,那么a和b会被存储在一起且a在b之前。
1.3 对齐填充
由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或者2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全
1.4 对象的计算
- 在32位系统下,存放Class指针的空间大小是4字节,MarkWord是4字节,对象头为8字节。
- 在64位系统下,存放Class指针的空间大小是8字节,MarkWord是8字节,对象头为16字节。
- 64位开启指针压缩的情况下,存放Class指针的空间大小是4字节,MarkWord是8字节,对象头为12字节。数组长度4字节+数组对象头8字节(对象引用4字节(未开启指针压缩的64位为8字节)+数组markword为4字节(64位未开启指针压缩的为8字节))+对齐4=16字节
- 静态属性不算在对象大小内。