对象在堆内存中布局
平常我们都在使用对象,现在从底层角度来分析下java对象的内存布局,以及对象布局各部分含义。
周志明老师JVM第三版的定义:
在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
对象在堆内存中的存储布局
对象内部结构分为:对象头、实例数据、对齐填充(保证8个字节的倍数)。
对象头分为对象标记(markOop)和类元信息(classOop),类元信息存储的是指向该对象类元数据(class)的首地址。
1. 对象头
对象标记Mark Word
用于存储对象自身的运行时数据,如哈希码(hashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等。占用空间大小根据JVM决定,为JVM的一个字大小,也就是32位JVM中Mark Word占用4个字节,64位JVM中占用8个字节64位。
为了节省空间,Mark Word是以非固定的数据结构来存储。具体数据结构如下:
32bit JVM:
在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节
默认存储对象的HashCode、分代年龄和锁标志位等信息。
这些信息都是与对象自身定义无关的数据,所以MarkWord被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化。
类元信息(又叫类型指针)
对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
2. 实例数据
存放类的属性(Field)数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。
3. 对齐填充
虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐这部分内存按8字节补充对齐。
官网理论
参考:
Hotspot术语表官网
底层源码理论证明
聊聊Object obj = new Object() new一个对象占多少内存空间
使用工具包可以查看对象在堆中的储存结构:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.16</version>
</dependency>
查看空类对象在堆中的储存结构
public class JOLDemo
{
public static void main(String[] args)
{
Object o = new Object();
//获取对象内部信息
System.out.println( ClassLayout.parseInstance(o).toPrintable());
}
}
OFFSET | 偏移量,也就是到这个字段位置所占用的byte数 |
---|---|
SIZE | 后面类型的字节大小 |
TYPE | 是Class中定义的类型 |
DESCRIPTION | DESCRIPTION是类型的描述 |
VALUE | VALUE是TYPE在内存中的值 |
GC年龄采用4位bit存储,最大为15,例如MaxTenuringThreshold参数默认值就是15
默认开启压缩说明
使用jvm 参数 -XX:+UseCompressedClassPointers
压缩指针默认是开启的
假如不压缩的情况?我们手动关闭压缩指针看看?
+是开启,-就是关闭,所以指令是-XX:-UseCompressedClassPointers
也要注意,不管是否开启压缩指针,创建一个对象就是16字节的。(开启压缩指针后缺失的会由对齐填充补充)