龙空技术网

JVM调优基础05:Java对象的内存布局

Seven的代码实验室 87

前言:

而今兄弟们对“对象和数组有什么区别”大体比较重视,各位老铁们都想要知道一些“对象和数组有什么区别”的相关资讯。那么小编同时在网摘上收集了一些对于“对象和数组有什么区别””的相关文章,希望你们能喜欢,小伙伴们一起来了解一下吧!

java代码经过编译器编译后变成class文件,通过类加载器被加载到Java运行时数据区域,最后经过执行引擎执行class字节码。

那么在整个过程中对象是如何被创建的呢?

java对象在内存中的布局又是怎样的呢?

对象的创建过程

比如创建一个T的对象:new T(),这个时候会经过以下几个步骤。

①把class loading到内存

②linking

verification校验

preparation把类的静态变量设置默认值

resolution做一个解析

③initializing把静态变量设为初始值同时执行静态语句块

④申请对象内存

⑤成员变量赋默认值

⑥调用构造方法

成员变量顺序赋初始值

执行构造方法语句

对象布局

注:本文中使用的jdk版本是1.8为基础的。

在HotSpot虚拟机中,对象在内存中的存储布局分为三块区域:对象头(Header)实例数据(Instance Data)对齐填充(Padding),如下:

普通对象与数组对象主要区别在于数组对象的对象头中多了一个4字节长度的Length属性,用于表示数组的长度。

对象头

Mark Word:包含一系列的标记位,比如轻量级锁的标记位,偏向锁标记位等等。在32位系统占4字节,在64位系统中占8字节;Class Pointer(类型指针):用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。在32位系统占4字节,在64位系统中占8字节;Length:如果是数组对象,还有一个保存数组长度的空间,占4个字节;

实例数据对象实例数据 : 对象的所有成员变量其中包含父类的成员变量和本类的成员变量,也就是说,除去静态变量和常量值放在方法区,非静态变量的值是随着对象存储在堆中的。byte、boolean是1个字节,short、char是2个字节,int、float是4个字节,long、double是8个字节,reference是4个字节(64位系统中是8个字节)。

对齐填充

由于 HotSpot VM 的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,也就是说对象的大小必须是 8 字节的整数倍。对象头部分是 8 字节的倍数,所以当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

Java对象占多少个字节?

一个java对象在内存中占几个字节呢?我们来算一下:

64位系统(未开启指针压缩):Mark Word占用8个字节 + Class Pointer占用8个字节 = 16个字节 (16已经是8的整数倍,所以不需要对齐填充)对象头的大小:16个字节64位系统(开启指针压缩):Mark Word 占用8个字节 + Class Pointer 占用4个字节 + 对齐填充 4个字节 = 16个字节 (空对象,所以实例数据大小为0)对象头的大小:12个字节

我们来看一下代码验证:

注意:使用:ClassLayout.parseInstance(o).toPrintable();

需要引入依赖

<dependency>  <groupId>org.openjdk.jol</groupId>  <artifactId>jol-core</artifactId>  <version>0.9</version></dependency>

(注意:JVM默认开启了指针压缩)

从上图我们可以看到在JVM开启了指针压缩的情况下符合上面的第二条计算公式:

64位系统(开启指针压缩):Mark Word 占用8个字节 + Class Pointer 占用4个字节 + 对齐填充 4个字节 = 16个字节 (空对象,所以实例数据大小为0)

对象头的大小:12个字节

下面我们来做关闭指针压缩测试,运行时VM参数设置:

-XX:-UseCompressedOops

可以看到由于关闭了指针压缩,64位系统(未开启指针压缩):Mark Word占用8个字节 + Class Pointer占用8个字节 = 16个字节 (16已经是8的整数倍,所以不需要对齐填充)

指针压缩

开启指针压缩使用算法开销能带来内存节约。

注意:32位HotSpot VM是不支持UseCompressedOops参数的,只有64位HotSpot VM才支持。

注意:下面的所有测试,都是开启了指针压缩的。

Example01:增加一个int属性

对象头:Mark Word 8个字节 + Class Pointer 4个字节 = 12字节

实例数据:int 占用 4个字节

ObjectSize = 对象头 + 实例数据 = 12 + 4 = 16字节(因为16已经是8的整数倍,固这里不用对齐补充)

Example02:测试对齐填充的位置

从上述例子中我们可以看到这个对象的大小是24字节。

我们会认为:

24字节 = 12字节(对象头)+5个字节(实例数据:byte 1个字节+String 引用 4个字节)+7个字节(对齐填充)

在最后通过7个字节来对齐填充。

但在 HotSpot VM 中,对象排布时,间隙是在 4 字节基础上的(在 32 位和 64 位压缩模式下)。

上述例子中,byte为一个字节,空隙只剩下 3 字节,接下来的 String 对象引用需要 4 字节来存放,因此 byte 和String对象引用之间就会有 3 字节对齐,在String对象引用排布后,最后会有 4 字节对齐。

因此结果上依然是对齐需要7个字节,但是对齐的位置排布应该是这样:

24字节 = 12字节(对象头)+【实例数据:(byte 1字节+3字节填充)+String 引用 4个字节)】+4个字节的对齐填充

Example03:字段重排序

通过例子我们可以看到,字段重新排序了,我们发现 short排在了最前面,byte在后面,然后是对齐填充(这里上面我们已经解释了原因),String排在最后。

结论:short\char 会排在 byte/boolean的前面

Example04:静态变量

对象实例数据instance data :对象的所有成员变量其中包含父类的成员变量和本类的成员变量,也就是说,除去静态变量和常量值放在方法区,非静态变量的值是随着对象存储在堆中的。

从例子中可以看到ObjectSize对象中有一个静态变量name,但是最终的对象大小是16字节(也就是12字节(对象头)+ byte1字节 + 3字节对齐填充),并没有包括静态变量那么的大小。

Example05:父类成员变量

从例子可以看到Test类继承了ObjectSize类,Test类中没有任何字段,但是从打印结果中可以看出Test类中包含了父类ObjectSize的byte字段b的大小。

最后,感谢阅读~

标签: #对象和数组有什么区别