01导读

这也是一篇填坑文章,填我之前在微头条上发的一道面试题,没有看过的小伙伴可以翻一翻我之前的微头条记录看一看,想一想这些问题该怎么回答,让后再来看这篇文章效果肯定会更好.

为什么对象会开辟存储空间(你真的清楚一个对象占多大的内存空间嘛)(1)

01

在java开发过程中,对象会贯穿着你整个职业生涯(java是一门面向对象的编程语言),在JVM调优或者一些大厂面试时有时可能也需要知道一个对象到底占多大的内存空间,对对象的大小很多人估计还停留在int占4个字节,double占8个字节,但是实际开发中一个对象可能会包括很多的基本类型和引用类型,比如下面的代码:

public class CalculateObjectSize { private int age; private String name; }

所以今天很有必要和大家一起分享一下计算一个对象到底占用多大的内存.

02基本的概念

首先想要计算一个对象占内存的大小,我们得清楚一个对象的占用内存的数据结构,如下图02:

为什么对象会开辟存储空间(你真的清楚一个对象占多大的内存空间嘛)(2)

02:针对64位系统

我上面画的这个对象数据结构是针对于64位系统来说的,在32位系统中类型指针(klass)占用4字节,markword也占用4字节,下面我所说的如果没有特别的标注都是针对64位系统(目前基本上所有的虚拟机都是64位)

接下来我们分别对对象头,实例数据,对齐填充进行深入的了解:

对象头:通过上面的图片我们可以看到对象头里面包含三个部分,一个是markword,一个是类型指针(klass),如果是数组的话则还存在一个保存数组长度的字段(占用4字节);

对于上面的概念我们可以大致总结一下:在开启指针压缩(默认是开启的)对象头的大小为:markword(8字节) klass(8字节)=16字节,而未开启指针压缩则为:markword(8字节) klass(4字节)=12字节。没有算上数组长度,这个你们计算时可以自行加上.

备注:这里还有一点需要注意的是指针压缩不能压缩markword.

实例数据:

所谓实例数据就是对象中存的有效信息,无论是从父类中继承来的,还是本身自己定义的都要记录下来,在JVM虚拟机中是固定大小的,如下图03:

为什么对象会开辟存储空间(你真的清楚一个对象占多大的内存空间嘛)(3)

04

上面这个图片还有一点需要注意的就是Reference的内存是4字节,这个是基于开启了指针压缩,如果没有开启指针压缩则是8字节。

对齐填充(padding):

对齐填充这玩意不是一定会存在的,它也没有什么特殊的含义仅仅只是一个占位符。因为虚拟机内存管理要求java对象内存的起始地址必须为8的整数倍,至于为什么必须为8的整数倍这里我就不再赘述,后续文章中肯定会提及,可以先关注一下,这里你只要记住不足8的整数倍时就会进行对齐填充。

备注:上面这个对齐填充说得还不够严谨,严格来说对齐填充会进行两次(基本数据类型需要一次对齐填充,对象的引用类型也需要一次),而且是按4字节对齐还是按8字节对齐这个还和是否开启了指针压缩,这个在下面的代码例子中我会说到。

03举个小栗子

首先我们在pom.xml文件中引入以下内容:

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

默认情况下是开启了指针压缩的,我们可以通过设置JVM参数来关闭指针压缩(参数:-XX:-UseCompressedOops),在IDEA中设置如下图05:

为什么对象会开辟存储空间(你真的清楚一个对象占多大的内存空间嘛)(4)

05

public class CalculateObjectSize { public static void main(String[] args) { System.out.println(ClassLayout.parseClass(CalculateObjectSize.class).toPrintable()); } }

关闭指针压缩运行结果如下图06:

为什么对象会开辟存储空间(你真的清楚一个对象占多大的内存空间嘛)(5)

06

开启指针压缩运行结果如下图07:

为什么对象会开辟存储空间(你真的清楚一个对象占多大的内存空间嘛)(6)

07

虽然开启指针压缩和关闭开启指针压缩的结果是相同的都是16字节,但是组成部分是完全不同的,在关闭指针压缩时为:8(markword) 8(klass)=16;开启指针压缩则是:8(markword) 4(klass) 4(padding)=16,这完全符合我们上面说的知识点。

public class CalculateObjectSize { String a; int b; short c; float d; public static void main(String[] args) { System.out.println(ClassLayout.parseClass(CalculateObjectSize.class).toPrintable()); } }

关闭指针压缩运行结果如下图08:

为什么对象会开辟存储空间(你真的清楚一个对象占多大的内存空间嘛)(7)

08

上图所示进行了对齐填充了一次在基本数据类型后面,因为16 4 4 2=26不是8的整数倍,所以下面就对齐填充了一个6,18 6=24正好是8的整数倍。

开启指针压缩运行结果如下图09:

为什么对象会开辟存储空间(你真的清楚一个对象占多大的内存空间嘛)(8)

09

通过上图09我们看到进行了两次对齐填充,一次在基本数据类型后面,一次在对象的尾部;

备注:这里还需要注意的一个点就是在关闭指针压缩时,在基本数据类型后面的对齐填充是以8字节对齐,而开启了指针压缩则是以4字节对齐;但是不管是有没有开启指针压缩对象尾部都是以8字节对齐。

public class CalculateObjectSize { String a; int b; short c; float d; public static void main(String[] args) { CalculateObjectSize[] a=new CalculateObjectSize[3]; System.out.println(ClassLayout.parseInstance(a).toPrintable()); } }

关闭指针压缩结果如下图10:

为什么对象会开辟存储空间(你真的清楚一个对象占多大的内存空间嘛)(9)

10

开启指针压缩的结果如下图11:

为什么对象会开辟存储空间(你真的清楚一个对象占多大的内存空间嘛)(10)

11

通过上面的图片我们可以发现不管是有没有开启指针压缩,如果是数组对象这个在对象头里面都会有一个内存为4字节来保存其数组长度,这也符合我们上面所说的。

04总结

通过上面的概念和代码例子我们清晰地分析了一个Java对象到底占用了多少的内存空间,这个不仅和操作系统有关系(64位还是32位),还和是否开启了指针压缩有关系(默认情况下是开启的),现在如果再有人问你一个Java对象占多大的内存空间你清楚了嘛?

为什么对象会开辟存储空间(你真的清楚一个对象占多大的内存空间嘛)(11)

,