题目
Java对象的内存结构分析
知识点
在JVM虚拟机种Java对象的内存结构如图所示分为三大块:对象头(Object Header)、实例数据(Instance Data)、对齐填充(Padding)。 对象头:标记字段、类型指针、数组长度(限于数组对象)。
- 对象头(Object header)
- Mark Word:对象的Mark Word部分占4个字节,其内容是一系列的标记位,比如轻量级的标记位(00),偏向锁标记位(01)等等。
- Class对象指针:Class对象指针的大小也是4个字节,其指向的位置是对象对应的Class对象(其对应的元数据对象)的内存地址。
- 对象实际数据:这里面包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,refrence是4个字节。
- 对齐填充:最后一部分是对齐填充的字节,按8个字节填充
需要注意的点:
对象头(Object Header)
Mark Word(标记字段)
用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID等。Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据自己的状态复用自己的存储空间。
Klass Pointer(类型指针)
对象指向它的类数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例;
Length(长度)
如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据。
因为虚拟机可以通过普通 Java 对象的元数据信息确定 Java 对象的大小,但是从数组的元数据中无法确定数组的大小。
在32 位JVM中,存放 Class 指针的空间大小是 4 字节,Mark Word 空间大小也是4字节,因此头部就是 8 字节,如果是数组就需要再加 4 字节表示数组的长度,如下表所示:
在 64 位 JVM 中,开启指针压缩,头部存放 Class 指针的空间大小还是4字节,而 Mark Word 区域会变大,变成 8 字节,也就是头部最少为 12 字节,如下图所示:
实例数据(Instance Data)
- 实例数据中存放了对象的字段信息。无论是从父类继承的,还是在子类中定义的,都保存在实例数据中。按照一定顺序存放,在满足这个顺序的条件下,父类定义的字段又会出现在子类定义的变量之前。
注意:这部分数据的存储顺序会受到虚拟机分配参数(FieldAllocationStyle)和字段在Java源码中定义顺序的影响。
HotSpot虚拟机默认的分配策略如下:
longs/doubles、ints、shorts/chars、bytes/booleans、oop(Ordinary Object Pointers) 复制代码
- 从分配策略中可以看出,相同宽度的字段总是被分配到一起
- 在满足这个前提的条件下,父类中定义的变量会出现在子类之前
CompactFields = true;
如果 CompactFields参数值为true(默认为true),那子类之中较窄的变量也可能会插入到父类变量的空隙之中。
- 对齐填充:对齐填充不是必然存在的,没有特别的含义,它仅起到占位符的作用。由于 HotSpot VM 的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,也就是说对象的大小必须是 8 字节的整数倍。对象头部分是 8 字节的倍数,所以当对象实例数据部分没有对齐时,就需要通过对齐填充来补全
拓展延伸
实际案例分析
注意:图中的markdown 写错了 应该是 markword ,请见谅!。
在HotspotJVM中,32位机器下,Integer对象的大小是int的几倍?
我们都知道在java语言规范已经规定了int的大小是4个字节,那么Integer对象的大小是多少呢?要知道一个对象的大小,那么必须需要知道对象在虚拟机中的结构是怎样的,来看看Hotsopt中对象在内存中的结构:
在上面这张图里面可以看出,对象在内存中的机构主要包含以下几个部分:
根据上面的图,那么我们可以得出Integer的对象的结构如下:
Integer只有一个int类型的成员变量value,所以其对象实际数据部分的大小是4个字节,然后再在后面填充4个字节达到8字节的对齐,所以可以得出Integer对象的大小是16个字节。
因此,我们可以得出Integer对象的大小是原生的int类型的4倍。
关于对象的内存结构,需要注意数组的内存结构和普通对象的内存结构稍微不同,因为数据有一个长度length字段。所以在对象头后面还多了一个int类型的length字段,占4个字节,接下来才是数组中的数据。如下图: