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 和对象引用之间就会有 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代码实验室】