来聊聊大厂面试题:求Java对象的大小

写在文章开头

日常使用Java进行业务开发时,我们基本不关心一个Java对象的大小,所以经常因为错误的估算导致大量的内存空间在无形之间被浪费了,所以今天笔者就基于这篇文章来聊聊一个Java对象的大小。

在这里插入图片描述

你好,我叫sharkchili,目前还是在一线奋斗的Java开发,经历过很多有意思的项目,也写过很多有意思的文章,是CSDN Java领域的博客专家,也是Java Guide的维护者之一,非常欢迎你关注我的公众号:写代码的SharkChili,这里面会有笔者精心挑选的并发、JVM、MySQL数据库专栏,也有笔者日常分享的硬核技术小文。

在这里插入图片描述

Java对象构成详解

整体构成概述

我们这里就以Hotspot虚拟机来探讨Java对象的构成,如下所示,可以看到Java对象的整体构成分为:

  1. 对象头(Header)
  2. 实例数据(Instance Data)
  3. 对齐填充(Padding)

在这里插入图片描述

对象头

Mark World

而对象头是由两部分组成的,第一部分用于存储对象自身的数据,也就是我们常说的Mark World,它记录着一个对象的如下信息:

  1. 哈希码(hashCode)
  2. GC分代年龄
  3. 锁状态标志
  4. 线程持有锁
  5. 偏向锁id
  6. 偏向时间戳
类型指针

再来说说类型指针,它记录着当前对象的元数据的地址,虚拟机可通过这个指针确定当前对象属于哪个类的实例,也就是说如果我们希望获得这个对象的元数据信息是可以通过类型指针定位到。
需要注意的是,在JDK8版本默认情况下,Mark World默认开启了指针压缩,这使得这一部分在64位的操作系统中的情况下,长度由原来的8个字节(64位)变为4个字节(32位)

数组长度

最后一部分就是数组长度,如果当前对象是基本类型的数组,那么这4位则是记录数组的长度,为什么说是基本类型呢?原因很简单,普通Java对象的的大小是可以通过元数据信息计算获得,而基本类型的数组却却无法从元数据信息中计算获得,所以我们就需要通过4个字节记录一下数组的长度以便计算。

实例数据

这一点就不多说了,这就是对象真正存储的有效信息,这些实例数据可以是从父类继承也可以是自定义字段,因为实例数据可能存在多个,Hotspot虚拟机定义了实例对象内存分配的先后顺序:

  1. long/double(8字节)
  2. int(4字节)
  3. shorts/chars(2字节)
  4. byte/boolean(1字节)
  5. oops(Ordinary Object Pointers 普通对象指针)

对齐填充

Hotspot虚拟机为了保证在指针压缩的情况下,32字节的空间仍然表示32G的内存空间地址,用到了8位对齐填充的思想,既保证了缓存命中率可以记录更多的对象,又能记录更多的对象地址。
因为指针压缩涉及的知识点比较多,笔者后续会单独开一个篇幅进行补充,这里我们有先说一下对其填充,假设我们现在有这样一个Java对象,可以看到在实例数据部分,它有8字节的long变量和4字节的int变量,合起来是12字节:

public class Obj {private long id;private int  age;
}

而8位对齐填充的意思就是实例数据部分的和要能够被16整除,所以对于这个对象的实例部分,我们还需要补充4个字节做到8位的对齐填充:

在这里插入图片描述

基于JOL了解Java对象的构成

前置步骤

了解了Java对象的组成之后,我们不妨通过JOL(Java Object Layout)来印证一下笔者的观点,所以我们需要在项目中引入下面这个依赖开始本次的实验:

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

空对象

首先是一个空对象EmptyObj ,可以看到这个对象没有任何成员变量:

class EmptyObj {}

我们都知道默认情况下,JDK8是开启指针压缩的,可以看到object header总共12字节,其中Mark World占了前8字节(4+4),类型指针占了4字节,加起来是12字节,而Java对象要求16位对齐,所以需要补齐4位,总的结果是16字节:

com.sharkChili.webTemplate.EmptyObj object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

我们再来看看关闭指针的压缩的结果,首先我们设置JVM参数将指针压缩关闭:

-XX:-UseCompressedClassPointers

此时我们就发现指针由原来是object header多了4位,原本被压缩的指针占用空间被还原了(offset为8-12的部分),总的计算结果为16字节,无需对齐填充:

com.sharkChili.webTemplate.EmptyObj object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)                           c0 34 b8 1c (11000000 00110100 10111000 00011100) (481834176)12     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

数组对象

我们再来看看数组对象,在默认开启指针压缩的情况下,我们创建了一个长度为3的数组:

 public static void main(String[] args) {int[] arrayObject = new int[]{1, 2, 3};// 打印对象大小System.out.println(ClassLayout.parseInstance(arrayObject).toPrintable());;}

可以看到Mark World还是占了8字节,指针4字节(offfset为8这一部分),而offset为12这一部分也有了4字节的空间,记录了一个值3即数组长度,其中8+4+4=16,对象头刚刚好8位对齐,故无需对齐填充。

再看看实例数据部分(offset为16)这一部分,因为数组中有3个整形所以长度size为12,需要补充4字节达到8位对齐,最终这个数组对象的长度为16(对象头)+16(实例数据部分)=32字节

[I object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)                           6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)12     4        (object header)                           03 00 00 00 (00000011 00000000 00000000 00000000) (3)16    12    int [I.<elements>                             N/A28     4        (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

我们再来看看关闭指针压缩的结果,可以看到mark word和指针都占了8位,加上数组长度的4位,最终对象头为20位,8位对齐后为24位。
同理实例部分还是12字节的数组元素大小加4字节的8对齐字节,关闭指针压缩后的对象大小为40字节:

[I object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)                           68 0b 85 1c (01101000 00001011 10000101 00011100) (478481256)12     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)16     4        (object header)                           03 00 00 00 (00000011 00000000 00000000 00000000) (3)20     4        (alignment/padding gap)                  24    12    int [I.<elements>                             N/A36     4        (loss due to the next object alignment)
Instance size: 40 bytes
Space losses: 4 bytes internal + 4 bytes external = 8 bytes total

带有成员变量的对象

我们再来说说带有成员变量的Java对象,也就是我们日常使用的普通Java对象:

class NormalObject {int a;short b;byte c;}

默认开启指针压缩的情况下,对象头为8+4=12字节,而实例数据部分,参考上文的实例数据顺序,我们的NormalObject的实例数据内存分配顺序为int、short、byte。
虚拟机为了更好的利用内存空间,看到对象头还差4字节才能保证对象头8位对齐填充,故将实例数据int作为对齐填充移动至对象头。

所以实例数据部分长度是2+1+5(对齐填充),最终在指针压缩的情况下,当前对象长度为24字节。

com.sharkChili.webTemplate.NormalObject object internals:OFFSET  SIZE    TYPE DESCRIPTION                               VALUE0     4         (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)4     4         (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4         (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)12     4     int NormalObject.a                            016     2   short NormalObject.b                            018     1    byte NormalObject.c                            019     5         (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 5 bytes external = 5 bytes total

同理,关闭指针压缩,相比读者现在也知道如何计算了,笔者这里就不多赘述了,答案是是对象头8+8,实例数据4+2+1+1(对齐填充),即关闭指针压缩情况下,当前普通对象大小为24字节:

com.sharkChili.webTemplate.NormalObject object internals:OFFSET  SIZE    TYPE DESCRIPTION                               VALUE0     4         (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)4     4         (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4         (object header)                           10 35 0b 1d (00010000 00110101 00001011 00011101) (487273744)12     4         (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)16     4     int NormalObject.a                            020     2   short NormalObject.b                            022     1    byte NormalObject.c                            023     1         (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 1 bytes external = 1 bytes total

带有数组的对象

最后我们再来看看带有数组的对象:

class NormalObject {int a;short b;byte c;int[] arr = new int[3];}

先来看看开启指针压缩8+4+int变量作为对齐填充即16字节,注意很多读者会认为此时还需要计算数组长度,实际上数组长度记录的是当前对象为数组情况下的数组的长度,而非成员变量的数组长度,所以我们的对象头总的大小就是16。

然后实例数据部分4+2+1+1(对齐填充),最后就是数组引用4+4(对齐填充),最终结果为16+8+8即32:

com.sharkChili.webTemplate.NormalObject object internals:OFFSET  SIZE    TYPE DESCRIPTION                               VALUE0     4         (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)4     4         (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4         (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)12     4     int NormalObject.a                            016     2   short NormalObject.b                            018     1    byte NormalObject.c                            019     1         (alignment/padding gap)                  20     4   int[] NormalObject.arr                          [0, 0, 0]
Instance size: 24 bytes
Space losses: 1 bytes internal + 0 bytes external = 1 bytes total

关闭指针压缩情况下,对象头8+8。实例数据4+2+1+1(对齐填充),再加上数组引用的4字节+4字对齐填充,最终计算结果为32字节。

com.sharkChili.webTemplate.NormalObject object internals:OFFSET  SIZE    TYPE DESCRIPTION                               VALUE0     4         (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)4     4         (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4         (object header)                           48 35 f8 1c (01001000 00110101 11111000 00011100) (486028616)12     4         (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)16     4     int NormalObject.a                            020     2   short NormalObject.b                            022     1    byte NormalObject.c                            023     1         (alignment/padding gap)                  24     4   int[] NormalObject.arr                          [0, 0, 0]28     4         (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 1 bytes internal + 4 bytes external = 5 bytes total

小结

总的来说要想获取Java对象的大小,我们只需按照如下步骤即可精确计算:

  1. mark world 8位。
  2. 确认是否开启指针压缩,以计算类型指针大小。
  3. 是否是数组,若是则增加4字节数组长度位。
  4. 计算对象头总和进行8位填充。
  5. 实例数据按照顺序排列并计算总和,并进行8位填充。
  6. 引用数据计算总和,并进行8位填充。
  7. 综合上述计算结果。

我是sharkchiliCSDN Java 领域博客专家开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号:
写代码的SharkChili,同时我的公众号也有我精心整理的并发编程JVMMySQL数据库个人专栏导航。

在这里插入图片描述

支付宝一面:一个 Java 对象到底有多大?被问傻眼了!!:https://mp.weixin.qq.com/s/7FdxwQIiccCb3nzJEA-m7g

聊一聊JAVA指针压缩的实现原理(图文并茂,让你秒懂):https://blog.csdn.net/liujianyangbj/article/details/108049482

<<面向面试官编程>>系列 – 如何计算 Java 对象大小:https://zhuanlan.zhihu.com/p/141967188

java对象头里都有什么:https://baijiahao.baidu.com/s?id=1717543131486015697#:~:text=这个方案是错误的,大家可以自行验证。 不管你设置jvm参数是:-XX%3A-UseCompressedOops还是-XX%3A%2BUseCompressedOops得到的数据都是如下这样: 验证-XX%3A-UseCompressedOops无效,这是因为klass pointer是类指针。 使用-XX%3A-UseCompressedOops参数无效,这个参数是针对对象的。

JVM的指针压缩:https://zhuanlan.zhihu.com/p/491684586

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/439464.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构与算法】之哈希表系列-20240129

这里写目录标题 一、217. 存在重复元素二、219. 存在重复元素 II三、242. 有效的字母异位词四、268. 丢失的数字五、290. 单词规律六、349. 两个数组的交集七、350. 两个数组的交集 II 一、217. 存在重复元素 简单 给你一个整数数组 nums 。如果任一值在数组中出现至少两次 &a…

力扣hot100 子集 回溯 超简洁

Problem: 78. 子集 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 参考题解 复杂度 时间复杂度: 添加时间复杂度, 示例&#xff1a; O ( n ) O(n) O(n) 空间复杂度: 添加空间复杂度, 示例&#xff1a; O ( n ) O(n) O(n) Code class Solution {List<Li…

闪测影像|智能影像测量仪高精度快速批量检测

在现代工业制造领域&#xff0c;快速批量测量零部件尺寸能确保产品质量、提升生产效率、优化生产过程、降低成本以及增强市场竞争力等。 通过快速批量测量&#xff0c;迅速检测出不合格的零部件&#xff0c;避免生产过程中的浪费和延误&#xff0c;优化生产过程并提高生产效率。…

MG7050HAN 基于声表的差分多输出 晶体振荡器 (HCSL)

基于MG7050 HAN的声表差分多输出晶体振荡器(HCSL)&#xff0c;采用两路或四路差分HCSL&#xff08;高速电流驱动逻辑&#xff09;输出&#xff0c;可以减少外部扇出缓冲区&#xff0c;特别适用于需要超低抖动、高频率范围内稳定工作的应用场合。其输出特性曲线超低抖动&#xf…

【C/C++ 05】快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序算法&#xff0c;其基本思想是&#xff1a;任取待排序序列中的某元素作为基准值&#xff0c;按照该基准值将待排序集合分割成两个子序列&#xff0c;左子序列中所有元素均小于基准值&#xff0c;右子序列中所有元素均大于…

集简云数据表新增动态下拉,一键拉取相关数据,快速实现业务场景自动化

为了提升数据表相关场景的数据交互的效率和准确性&#xff0c;本周集简云数据表新增了动态下拉字段&#xff0c;可直接在该字段中关联应用动作获取&#xff0c;无需搭建复杂流程&#xff0c;可搭配按钮使用&#xff0c;直接调用和配置应用动作获取相关字段数据&#xff0c;手动…

MySQL前百分之N问题--percent_rank()函数

PERCENT_RANK()函数 PERCENT_RANK()函数用于将每行按照(rank - 1) / (rows - 1)进行计算,用以求MySQL中前百分之N问题。其中&#xff0c;rank为RANK()函数产生的序号&#xff0c;rows为当前窗口的记录总行数 PERCENT_RANK()函数返回介于 0 和 1 之间的小数值 selectstudent_…

elementui中的tree自定义图标

需求&#xff1a;实现如下样式的树形列表 自定义树的图标以及点击时&#xff0c;可以根据子级的关闭&#xff0c;切换图标 <el-tree :data"treeList" :props"defaultProps"><template #default"{ node, data }"><span class&quo…

Mac安装及配置MySql及图形化工具MySQLworkbench安装

Mac下载配置MySql mysql下载及安装 下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 根据自己电脑确定下载x86还是ARM版本的 如果不确定&#xff0c;可以查看自己电脑版本&#xff0c;终端输入命令 uname -a 点击Download下载&#xff0c;可跳过登录注册&…

【论文阅读】Long-Tailed Recognition via Weight Balancing(CVPR2022)

目录 论文使用方法weight decayMaxNorm 如果使用原来的代码报错的可以看下面这个 论文 问题&#xff1a;真实世界中普遍存在长尾识别问题&#xff0c;朴素训练产生的模型在更高准确率方面偏向于普通类&#xff0c;导致稀有的类别准确率偏低。 key:解决LTR的关键是平衡各方面&a…

VRRP协议原理

目录 VRRP的产生单网关的缺陷多网关存在的问题VRRP基本概述VRRP基本结构状态机 VRRP主备备份工作过程VRRP的工作过程如果Master发生故障&#xff0c;则主备切换的过程如果原Master故障恢复&#xff0c;则主备回切的过程 VRRP联动功能 VRRP负载分担工作过程 VRRP的产生 单网关的…

网络体系结构 和网络原理之UDP和TCP

目录 网络分层 一. 应用层 http协议 二. 传输层 1. 介绍 2.UDP协议 (1)组成 (2)细节 3.TCP协议 (1)特性如下链接&#xff1a; (2)组成 (3)特点 三. 网络层 四. 数据链路层 1.介绍 2.以太网协议 3.mac地址和ip地址 五. 物理层 DNS 网络分层 一. 应用层 应用程序 现成的…