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

news2024/11/17 16:02:32

写在文章开头

日常使用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.coloradmin.cn/o/1419696.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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

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

【深度优先搜索】【组合数学】【动态规划】1467.两个盒子中球的颜色数相同的概率

作者推荐 【动态规划】【字符串】【行程码】1531. 压缩字符串 本文涉及知识点 动态规划汇总 深度优先搜索 组合数学 LeetCode1467 两个盒子中球的颜色数相同的概率 桌面上有 2n 个颜色不完全相同的球&#xff0c;球上的颜色共有 k 种。给你一个大小为 k 的整数数组 balls …

数据写入HBase(scala)

package sourceimport org.apache.hadoop.hbase.{HBaseConfiguration, TableName} import org.apache.hadoop.hbase.client.{ConnectionFactory, Put} import org.apache.hadoop.hbase.util.Bytesobject ffff {def main(args: Array[String]): Unit {//hbase连接配置val conf …

c++连接mysql

c连接mysql 安装mysql以及c对应的库进入数据库&#xff0c;创建数据库&#xff0c;表&#xff0c;并新建管理员用户编写c代码编译运行&#xff0c;测试结果头文件解释 安装mysql以及c对应的库 sudo apt-get update sudo apt-get install mysql-server sudo apt-get install li…

2023年算法CDO-CNN-BiLSTM-ATTENTION回归预测(matlab)

2023年算法CDO-CNN-BiLSTM-ATTENTION回归预测&#xff08;matlab&#xff09; CDO-CNN-BiLSTM-Attention切诺贝利灾难优化器优化卷积-长短期记忆神经网络结合注意力机制的数据回归预测 Matlab语言。 切诺贝利灾难优化器Chernobyl Disaster Optimizer (CDO)是H. Shehadeh于202…

新书推荐——《趣读数字经济》

文章目录 缘起:“躺嬴”的一天/ 001 第1章 名花解语,石心铁肠&#xff0c;当属“人工智能”/ 009 1.1 自学成才的人工智能/ 011 1.2 狂飙的话病ChatGPT / 017 1.3 算力、算法与数据:人工智能的核心/ 026 1.4 人工智能会抢走我们的饭碗吗/032 1.5 人工智能有多能/ 036 1.6 AI…

地址解析工具---AddressParseUtil

一、工具源码 package com.rural_vibration.common.utils;import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern;/*** description: 地址解析工具 <…

FTP服务之WindowsServer2019中搭建私有FTP服务器

WindowsServer2019搭建FTP服务器 文章目录 WindowsServer2019搭建FTP服务器1. 查看FTP服务是否开启2. 配置FTP服务站点3. 访问 1. 查看FTP服务是否开启 WindowsServer2019默认是开启FTP服务的&#xff0c;如果未开启&#xff0c;则按下面步骤开启即可 打开服务器管理 添加角色和…

java OA办公自动化系统

java OA办公自动化系统&#xff0c;java项目&#xff0c;springboot项目。eclipse和idea都能打开运行。 前端技术&#xff1a;Bootstrap&#xff0c;Jquery&#xff0c;My97 DatePicker&#xff0c;kindeditor&#xff0c;freemarker 后端技术&#xff1a;SpringBoot&#xf…

KVM 内存概述

KVM 内存概述 CPU缓存基本概念内存基本概念EPT和VPID内存过载使用大页透明大页透明大页使用 KSM NUMA CPU缓存基本概念 CPU工作过程中会直接读取内存的数据&#xff0c;而大部分同学对内存的感觉是内存条的一个概念&#xff0c;其实CPU中也有内存的概念&#xff0c;称之为L1-L…

langchain + hugginface入门体验

简介 本文记录一次使用langchain调用openai并部署在huggingface上的经历 安装环境依赖 我的python版本是3.9 pip install langchain pip install openai代码 app.py import streamlit as st # from langchain_community.chat_models import ChatOpenAI from langchain_openai …

qt5-入门

参考&#xff1a; qt学习指南 Qt5和Qt6的区别-CSDN博客 Qt 学习之路_w3cschool Qt教程&#xff0c;Qt5编程入门教程&#xff08;非常详细&#xff09; 本地环境&#xff1a; win10专业版&#xff0c;64位 技术选择 Qt5力推QML界面编程。QML类似HTML&#xff0c;可以借助CSS进…

【大数据】Flink SQL 语法篇(二):WITH、SELECT WHERE、SELECT DISTINCT

Flink SQL 语法篇&#xff08;二&#xff09; 1.WITH 子句2.SELECT & WHERE 子句3.SELECT DISTINCT 子句 1.WITH 子句 应用场景&#xff08;支持 Batch / Streaming&#xff09;&#xff1a;With 语句和离线 Hive SQL With 语句一样的&#xff0c;语法糖 1&#xff0c;使用…

15.Golang中的反射机制及应用

目录 概述实践基本应用复杂应用 结束 概述 Golang中的反射用法还是比较简单的 reflect.TypeOf(arg)reflect.ValueOf(arg) 实践 基本应用 package mainimport ("fmt""reflect" )func reflectNum(arg interface{}) {fmt.Println("type ", re…

专科拿到季军:微茫星火,奋起直追!

Datawhale干货 作者&#xff1a;“不啻微茫”团队&#xff0c;季军方案 前 言 大家好&#xff0c;我们是 飞桨星河社区 X 智海Mo平台 AI 大模型创意应用大赛 获奖团队——"不啻微茫"&#xff0c;很荣幸能有机会与大家分享这次比赛经验&#xff0c;我们从零开始的过程…

python打造光斑处理系统2:打开图像和默认图像

文章目录 打开图像默认图像 光斑处理&#xff1a;python处理高斯光束的图像 光斑处理系统&#xff1a;程序框架 打开图像 光斑图像的本质是光强在空间中的分布&#xff0c;而有的时候&#xff0c;通过CCD拍到的图像往往存成虚假的RGB格式&#xff0c;所以在打开图像时&#x…

AI大语言模型学习笔记之三:协同深度学习的黑魔法 - GPU与Transformer模型

Transformer模型的崛起标志着人类在自然语言处理&#xff08;NLP&#xff09;和其他序列建模任务中取得了显著的突破性进展&#xff0c;而这一成就离不开GPU&#xff08;图形处理单元&#xff09;在深度学习中的高效率协同计算和处理。 Transformer模型是由Vaswani等人在2017年…

【机器学习300问】20、什么是神经网络?和深度学习什么关系?

在学习深度学习的相关知识之前&#xff0c;我们首先得了解什么是神经网络&#xff0c;解开神经网络的神秘面纱后&#xff0c;什么是深度学习的问题也就迎刃而解。我依旧会采用我习惯的方式&#xff1a;先给出例子直观理解&#xff0c;在给出定义深入理解&#xff0c;最后在实际…

HarmonyOS NEXT 星河版项目案例

参考代码&#xff1a;HeimaHealthy: 鸿蒙项目案例练习 (gitee.com) 1.欢迎页面 Entry Component struct WelcomePage {State message: string Hello Worldbuild() {Column({space: 10}) {Row() {// 1.中央slogonImage($r(app.media.home_slogan)).width(260)}.layoutWeight(…

二叉搜索树的后序遍历序列

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…