来聊聊大厂面试题:求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                               VALUE
      0     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                               VALUE
      0     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                               VALUE
      0     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/A
     28     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                               VALUE
      0     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/A
     36     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                               VALUE
      0     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                            0
     16     2   short NormalObject.b                            0
     18     1    byte NormalObject.c                            0
     19     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                               VALUE
      0     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                            0
     20     2   short NormalObject.b                            0
     22     1    byte NormalObject.c                            0
     23     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                               VALUE
      0     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                            0
     16     2   short NormalObject.b                            0
     18     1    byte NormalObject.c                            0
     19     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                               VALUE
      0     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                            0
     20     2   short NormalObject.b                            0
     22     1    byte NormalObject.c                            0
     23     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.mfbz.cn/a/357481.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

部分地级市收入泰尔指数数据,shp/excel格式,附数据可视化图及计算公式

泰尔指数的计算方式&#xff1a;分别计算城镇和农村收入份额与人口份额之比的自然对数&#xff0c;然后再以城乡收入份额作为权数&#xff0c;进行加权平均求和。 数据名称: 部分地级市收入泰尔指数数据 数据格式: Shp、excel 数据时间: 2010-2019年 数据几何类型: 面 数…

c++学习记录 多态—案例2—电脑组装

#include<iostream> using namespace std;//抽象不同的零件//抽象的cpu类 class Cpu { public://抽象的计算函数virtual void calculate() 0; };//抽象的显卡类 class VideoCard { public://抽象的显示函数virtual void display() 0; };//抽象的内存条类 class Memory …

【蓝桥杯日记】复盘篇二:分支结构

前言 本篇笔记主要进行复盘的内容是分支结构&#xff0c;通过学习分支结构从而更好巩固之前所学的内容。 目录 前言 目录 &#x1f34a;1.数的性质 分析&#xff1a; 知识点&#xff1a; &#x1f345;2.闰年判断 说明/提示 分析&#xff1a; 知识点&#xff1a; &am…

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

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

dfs专题 P1255 数楼梯——洛谷(疑问)

题目描述 楼梯有 &#xfffd;N 阶&#xff0c;上楼可以一步上一阶&#xff0c;也可以一步上二阶。 编一个程序&#xff0c;计算共有多少种不同的走法。 输入格式 一个数字&#xff0c;楼梯数。 输出格式 输出走的方式总数。 输入输出样例 输入 #1复制 4 输出 #1复制…

【Python】Win11用GTK3实现多文档窗体弹出对话框

一、安装PyGObject PyGObject是GTK的Python绑定&#xff0c;用于Python中的GTK3程序。可以使用pacman命令来安装PyGObject、GTK3和其他必要的库。 1. 打开MSYS2终端&#xff1a; 可以直接通过MSYS2的快捷方式打开终端或者从开始菜单中找到MSYS2。 2. 更新MSYS2包管理器&…

回显服务器(基于UDP)

目录 基本概念 API学习 DatagramSocket DatagramPacket InetSocketAddress 回显服务器实现 服务端 思路分析 具体实现 完整代码 客户端 思路分析 具体实现 完整代码 运行测试 基本概念 发送端和接收端 在一次网络数据传输时&#xff1a; 发送端&#xff1a;…

Linux中的软链接与硬链接

Linux链接概念 Linux链接分两种&#xff0c;一种被称为硬链接&#xff08;Hard Link&#xff09;&#xff0c;另一种被称为符号链接&#xff08;Symbolic Link&#xff09;。默认情况下&#xff0c;使用 ln 命令不加参数创建硬链接&#xff0c;加 -s 参数则创建软链接 硬链接…

2024不可不会的StableDiffusion之扩散模型(四)

1. 引言 这是我关于StableDiffusion学习系列的第四篇文章&#xff0c;如果之前的文章你还没有阅读&#xff0c;强烈推荐大家翻看前篇内容。在本文中&#xff0c;我们将学习构成StableDiffusion的第三个基础组件基于Unet的扩散模型&#xff0c;并针该组件的功能进行详细的阐述。…

RK3568平台开发系列讲解(Linux系统篇)platform 设备的注册

🚀返回专栏总目录 文章目录 一、platform_device_register 注册函数二、platform_device_unregister 反注册函数三、platform_device 结构体四、resource 结构体沉淀、分享、成长,让自己和他人都能有所收获!😄 一、platform_device_register 注册函数 platform_device_re…

海外云手机运营Instagram攻略

Instagram是世界著名的社交媒体平台&#xff0c;有着10亿实时用户&#xff0c;是跨境电子商务的优质流量来源。平台以女性用户为主&#xff0c;购物倾向高&#xff0c;转化率好。它被公认为外贸行业的优质社交媒体流量池。那么&#xff0c;如何使用海外云手机吸引Instagram上的…

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

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

力扣题集(第一弹)

一日练,一日功;一日不练十日空。 学编程离不开刷题&#xff0c;接下来让我们来看几个力扣上的题目。 1. 242. 有效的字母异位词 题目描述 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 注意&#xff1a;若 s 和 t 中每个字符出现的次数…

JS图片二维码识别

前言 js识别QR图片&#xff0c;基于jsQR.js 代码 <!DOCTYPE html> <html> <head><meta charset"utf-8" /><title>图片二维码识别</title><script src"https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js">…

什么是消息队列?

消息用队列的模式发送&#xff0c; 把要传输的数据放在队列中&#xff0c; 产生消息的叫做生产者&#xff0c; 从队列里取出消息的叫做消费者。 一、组成 生产者&#xff1a;Producer 消息的产生者与调用端 主要负责消息所承载的业务信息的实例化 是一个队列的发起方 代理…

网站小程序分类目录网源码系统+会员注册登录功能 附带完整的搭建教程

随着互联网的发展&#xff0c;小程序分类目录网站已经成为了人们获取各类信息的重要渠道。而在这个领域中&#xff0c;罗峰给大家分享一款网站小程序分类目录网源码系统以其强大的功能和易用性&#xff0c;脱颖而出。本系统集成了会员注册登录功能&#xff0c;让用户能够更加便…

uniapp H5 实现上拉刷新 以及 下拉加载

uniapp H5 实现上拉刷新 以及 下拉加载 1. 先上图 下拉加载 2. 上代码 <script>import DragableList from "/components/dragable-list/dragable-list.vue";import {FridApi} from /api/warn.jsexport default {data() {return {tableList: [],loadingHi…

Redis核心技术与实战【学习笔记】 - 6.Redis 的统计操作处理

1.前言 在 Web 业务场景中&#xff0c;我们经常保存这样一种信息&#xff1a;一个 key 对应了一个数据集合。比如&#xff1a; 手机 APP 中的每天用户登录信息&#xff1a;一天对应一系列用户 ID。电商网站上商品的用户评论列表&#xff1a;一个商品对应了一些列的评论。用户…

12 数据仓库理论

数仓基本概述 数据仓库基本概念 数据仓库是一个为数据分析而设计的企业级数据管理系统。数据仓库可集中 、整合多个信息源的大量数据。 数仓核心架构 数据仓库建模概述 数据仓库建模意义 数据模型就是数据组织和存储方法&#xff0c;它强调从业务、数据存取和使用角度合理…

Django配置websocket时的错误解决

基于移动群智感知的网络图谱构建系统需要手机app不断上传数据到服务器并把数据推到前端标记在百度地图上&#xff0c;由于众多手机向同一服务器发送数据&#xff0c;如果使用长轮询&#xff0c;则实时性差、延迟高且服务器的负载过大&#xff0c;而使用websocket则有更好的性能…