1 对象组成
Java对象由三部分组成,对象头、对象实例数据和填充数据,其中,填充数据不一定存在,只有需要填充时,才有,Java对象结构如图1.1所示。
2 对象头
2.1 markword
存储对象自身运行时数据,入口HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,
该部分的长度在32位和64位虚拟机(未开启压缩指针)中分别为32bit和64bit,即MarkWord。
2.1.1 无锁
锁状态 | 25位 | 31位 | 1位 | 4位 | 1位(偏向锁位) | 2位(锁标志) |
---|---|---|---|---|---|---|
无锁 | unused | hashCode(调用hashCode方法时填充) | unused | 分代年龄 | 0 | 01 |
2.1.2 偏向锁
锁状态 | 54位 | 2位 | 1位 | 4位 | 1位(偏向锁位) | 2位(锁标志) |
---|---|---|---|---|---|---|
偏向锁 | 当前线程指针 | Epoch | unused | 分代年龄 | 1 | 01 |
2.1.3 轻量级锁(自旋锁)
锁状态 | 62位 | 2位(锁标志) |
---|---|---|
轻量级锁 | 指向线程栈中LockRecord的指针(通过自旋竞争锁CAS,有次数上限) | 00 |
JDK1.6前:默认10次自旋,-XX:PreBlockSpin配置,或者超过CPU核数的一半,自动升级重量级锁 。
JDK1.6之后,自适应自旋(Adaptive Self Spinning),JVM自动调整。
2.1.4 重量级锁
锁状态 | 62位 | 2位(锁标志) |
---|---|---|
重量级锁 | 指向互斥量(重量级锁)的指针 | 10 |
2.2 klass
klass类型指针,即对象指向类元数据的指针,虚拟机通过这个指针确定该对象是哪个类的实例。
2.3 数组长度
只有数组对象有数组长度,如果对象为数组,对象头中必须记录数组长度。
3 对象实例数据
对象实例数据是对象的有效数据,包括对象中的各种类型的属性、方法等。
4 填充
填充数据不是必须存在的,没有特别的含义,只是占位,HotSpot中VM内存管理系统要求对象起始地址必须是8字节的整数倍,
即对象大小必须是8字节的整数倍,对象头是8字节的1或2倍。
5 对象大小
对象大小计算公式:
对象大小
=
对象头
+
实例数据
+
(填充数据)
对象大小=对象头+实例数据+(填充数据)
对象大小=对象头+实例数据+(填充数据)
(1)64位操作系统
(2)32G内存以下,默认开启对象指针压缩
对象头:12字节=8字节(mark)+4字节(Klass指针)
(1)64位操作系统
(2)32G内存以上,关闭对象指针压缩
对象头:16字节=8字节(mark)+8字节(Klass指针)
类中的静态变量、静态方法不计入对象大小。
静态方法、静态变量在方法区,不在堆中;
对象存储在堆中。
新建空对象
public class ObjectLayoutTest {
public static void main(String[] args) {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
对象大小:16字节,
结果如下图:
mark部分:8字节
class部分:4字节(压缩)
填充部分:4字节
填充部分补齐class部分。
当前的JDK版本是64位,即8字节,保证性能(设计上),8个字节是最小单元,分配的内存空间应是8字节的倍数。
所以新建空对象,无论是否开启指针压缩,对象大小都是16字节。
6 静态方法与静态变量
静态方法与静态变量存储在方法区中,对象存储在堆中,JDK8的JVM简略结构如图6.1所示。
因此,静态方法和静态变量不计入对象大小。
【参考文献】
[1]https://www.cnblogs.com/maxigang/p/9040088.html
[2]https://blog.csdn.net/antony9118/article/details/54317637
[3]https://baijiahao.baidu.com/s?id=1636927461989417537&wfr=spider&for=pc