JAVA和Nginx 教程大全

网站首页 > 精选教程 正文

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

wys521 2024-12-18 15:23:57 精选教程 27 ℃ 0 评论

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的大小。


最后,感谢阅读~



Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表