对象的内存布局
在Hotspot虚拟机里,对象在内存中的内存布局可以划分为三个部分:对象头、实例数据以及对齐填充。
- 对象头:包括了对象的类型、GC信息、锁状态和哈希码等基本信息。
- 实例数据:主要是存放对象的类自身属性信息以及父类的属性信息,如果一个类没有字段属性,就不需要实例数据域
- 对齐填充数据:虚拟机规范要求每个对象所占内存字节数必须是 8 的倍数。
对象头:对象头主要包含三个部分
- Mark Word:主要包含 锁的标记、对象GC年龄、哈希码、偏向锁等信息。
- Class Pointer:指向对象所属的 Class 在元数据区的内存指针,通常在32位系统占4字节,在64位系统中占8字节,64位 JVM 在 1.6 版本后默认开启了压缩指针 占用4个字节
- Length:如果对象是数组,还需要一个保存数组长度的空间,占 4 个字节
Mark Word 结构如下图所示
实例数据:实例数据里面主要是对象的字段数据信息。
对齐填充:JVM 堆中所有对象分配的内存字节总数必须是 8 的倍数,如果对象头和实例数据占用的总大小不满足要求,则需要通过对齐数据来填满
- 计算机处理器读取内存数据时不是逐个字节去访问,而是以内存访问粒度(2、4、8、16 字节的块) 为单位访问内存。这样做的目的是减少内存访问次数,加快执行速度
- JVM 内存对齐的原因:基于内存访问方式,对于未对齐地址的内存数据,处理器一次访问非常可能在这块内存中取到不需要的数据,必须额外做一些移除不需要的数据的工作,再将其放置在寄存器中,这会严重影响内存访问效率,JVM 要求内存对齐是为了计算机高效寻址,快速读取对象数据
代码示列:
static class User{
private Long id;
private String name;
}
public static void main(String[] args){
User user = new User();
System.out.println(ClassLayout.parseInstance(user).toPrintable());
}
对象中各个部分内存占用如下:
- 对象头中 Mark Word 占用 8 字节,类对象指针占用 4 字节,总共占用 12 字节
- User对象的属性 id 和 name 各占 4 字节,总共占用 8 字节
- 对齐填充以上两个部分总共占用 20 字节, 不满足 8 的倍数,所以需要填充 4 字节。
数组对象示例:
static class User{
private Long id;
private String name;
}
public static void main(String[] args){
User user1 = new User();
User user2 = new User();
User[] users = new User[]{user1, user2};
System.out.println(ClassLayout.parseInstance(users).toPrintable());
}
对象中各个部分内存占用如下:
- 对象头中 Mark Word 占用 8 字节,类对象指针占用 4 字节,总共占用 12 字节
- 数组长度 & 对齐填充数据 数组长度为 2,占用 4 字节
- 实例数据 数组有两个User 对象,引用指针 共占用 8 字节,至此数组对象总大小 24 字节,满足 8 的倍数,因此不需要填充。