JVM调优实战:六、Java对象的内存布局

java代码经过编译器编译后变成class文件,通过类加载器被加载到Java运行时数据区域,最后经过执行引擎执行class字节码。
那么在整个过程中对象是如何被创建的呢?

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

java代码执行的大致流程

对象的创建过程

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

①把class loading到内存
linking
verification校验
preparation把类的静态变量设置默认值
resolution做一个解析
③initializing把静态变量设为初始值同时执行静态语句块
④申请对象内存
⑤成员变量赋默认值
⑥调用构造方法
成员变量顺序赋初始值
执行构造方法语句

对象布局

注:本文中使用的jdk版本是1.8为基础的。
在 HotSpot虚拟机中,对象在内存中的存储布局分为三块区域:对象头(Header)实例数据(Instance Data)对齐填充(Padding),如下:
Java对象布局
普通对象与数组对象主要区别在于数组对象的对象头中多了一个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属性

添加一个int变量a

对象头: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 和对象引用之间就会有 3 字节对齐,对象引用排布后,最后会有 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的大小。

以上就是本章节的全部内容,更多干货文章欢迎订阅我的同名公众号【Seven代码实验室
Seven的代码实验室

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Seven的代码实验室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值