百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Java-对象到底占多少个字节?计算规则是什么?

bigegpt 2025-03-02 16:14 9 浏览

JAVA对象模型

我们先了解一下,一个JAVA对象的存储结构。在Hotspot虚拟机中,对象在内存中的存储布局分为 3 块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

java 对象的大小默认是按照 8 字节对齐,也就是说 Java 对象的大小必须是 8 字节的倍数。若是算到最后不够 8 字节的话,那么就会进行对齐填充。

那么为何非要进行 8 字节对齐呢?这样岂不是浪费了空间资源?

其实不然,由于 CPU 进行内存访问时,一次寻址的指针大小是 8 字节,正好也是 L1 缓存行的大小。如果不进行内存对齐,则可能出现跨缓存行的情况,这叫做 缓存行污染。

当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。

对象头(Header)

对象头,又包括三部分:Mark Word、(Klass Word)元数据指针、数组长度。

MarkWord:用于存储对象运行时的数据,比如HashCode、锁状态标志、GC分代年龄等。这部分在64位操作系统下,占8字节(64bit),在32位操作系统下,占4字节(32bit)。

Mark Word布局:

Klass Word:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

这部分就涉及到一个指针压缩的概念,在开启指针压缩的情况下,占4字节(32bit),未开启情况下,占8字节(64bit),现在JVM在1.6之后,在64位操作系统下都是默认开启的。

数组长度:这部分只有是数组对象才有(int[]),如果是非数组对象,就没这部分了,这部分占4字节(32bit)。

内存对齐

想要知道为什么虚拟机要进行对齐填充,我们需要了解什么是内存对齐?在开发人员眼中,我们看到的内存是这样的:

上图表示一个坑一个萝卜的内存读取方式。但实际上 CPU 并不会以一个一个字节去读取和写入内存。相反, CPU 读取内存是一块一块读取的,块的大小可以为 2、4、6、8、16 字节等大小。块大小我们称其为内存访问粒度。如下图:

假设一个32位平台的 CPU,那它就会以4字节为粒度去读取内存块。那为什么需要内存对齐呢?主要有两个原因:

平台(移植性)原因:不是所有的硬件平台都能够访问任意地址上的任意数据。例如:特定的硬件平台只允许在特定地址获取特定类型的数据,否则会导致异常情况。

性能原因:若访问未对齐的内存,将会导致 CPU 进行两次内存访问,并且要花费额外的时钟周期来处理对齐及运算。而本身就对齐的内存仅需要一次访问就可以完成读取动作。

下面用图例来说明 CPU 访问非内存对齐的过程:

在上图中,假设CPU 是一次读取4字节,在这个连续的8字节的内存空间中,如果我的数据没有对齐,存储的内存块在地址1,2,3,4中,那CPU的读取就会需要进行两次读取,另外还有额外的计算操作:

1、CPU 首次读取未对齐地址的第一个内存块,读取 0-3 字节。并移除不需要的字节 0。

2、CPU 再次读取未对齐地址的第二个内存块,读取 4-7 字节。并移除不需要的字节 5、6、7 字节。

3、合并 1-4 字节的数据。

4、合并后放入寄存器。

所以,没有进行内存对齐就会导致CPU进行额外的读取操作,并且需要额外的计算。如果做了内存对齐,CPU可以直接从地址0开始读取,一次就读取到想要的数据,不需要进行额外读取操作和运算操作,节省了运行时间。我们用了空间换时间,这就是为什么我们需要内存对齐。

回到Java空对象填充了4个字节的问题,因为原字节头是12字节,64位机器下,内存对齐的话就是128位,也就是16字节,所以我们还需要填充4个字节。(64位机器一次读取8字节,因为64位下填充为8字节的整数倍,这里12字节,显然填充到16字节效果最佳。)

内存消耗演示


            org.openjdk.jol
            jol-core
            0.9
        

空对象(16byte)

public static class Dog {
 
    }

    public static void main(String[] args) {
 
        Dog dog =new Dog();
        System.out.println(ClassLayout.parseInstance(dog).toPrintable());
    }

明明是12为啥变成16了, 这个就是使用了内存对齐了, 8的倍数

基本数据类型(1~8byte)

public static class Dog {
 
        int a;
        long a1;
        double a2;
        char a3;
        float a4;
        boolean a5;
        short a6;
        byte a7;
    }

    public static void main(String[] args) {
 
        Dog dog =new Dog();
        System.out.println(ClassLayout.parseInstance(dog).toPrintable());
    }

引用地址(4字节)

public static class Dog {
 
        Date a;
        String a1;
        Dog a2;

    }
    public static void main(String[] args) {
 
        System.out.println(ClassLayout.parseInstance(new Dog()).toPrintable());
    }

任何非基本类型的变量都存储的是对象的引用地址,而在java中地址是使用4字节存储的

包装类型和String(16~24byte)

public static void main(String[] args) {
 
        Integer a=1;
        System.out.println(ClassLayout.parseInstance(a).toPrintable());
        Long a1=12313123L;
        System.out.println(ClassLayout.parseInstance(a1).toPrintable());
        Double a2=1.1;
        System.out.println(ClassLayout.parseInstance(a2).toPrintable());
        Character a3='a';
        System.out.println(ClassLayout.parseInstance(a3).toPrintable());
        Float a4=1.1F;
        System.out.println(ClassLayout.parseInstance(a4).toPrintable());
        Boolean a5=true;
        System.out.println(ClassLayout.parseInstance(a5).toPrintable());
        Short a6=1;
        System.out.println(ClassLayout.parseInstance(a6).toPrintable());
        Byte a7=1;
        System.out.println(ClassLayout.parseInstance(a7).toPrintable());
        String a8="xxxx";
        System.out.println(ClassLayout.parseInstance(a8).toPrintable());

    }
java.lang.Integer 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)                           67 22 00 f8 (01100111 00100010 00000000 11111000) (-134208921)
     12     4    int Integer.value                             1
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

java.lang.Long object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           ae 22 00 f8 (10101110 00100010 00000000 11111000) (-134208850)
     12     4        (alignment/padding gap)                  
     16     8   long Long.value                                12313123
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

java.lang.Double object internals:
 OFFSET  SIZE     TYPE DESCRIPTION                               VALUE
      0     4          (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4          (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4          (object header)                           92 21 00 f8 (10010010 00100001 00000000 11111000) (-134209134)
     12     4          (alignment/padding gap)                  
     16     8   double Double.value                              1.1
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

java.lang.Character 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)                           c6 20 00 f8 (11000110 00100000 00000000 11111000) (-134209338)
     12     2   char Character.value                           a
     14     2        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total

java.lang.Float object internals:
 OFFSET  SIZE    TYPE DESCRIPTION                               VALUE
      0     4         (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4         (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4         (object header)                           4b 21 00 f8 (01001011 00100001 00000000 11111000) (-134209205)
     12     4   float Float.value                               1.1
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

java.lang.Boolean 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)                           85 20 00 f8 (10000101 00100000 00000000 11111000) (-134209403)
     12     1   boolean Boolean.value                             true
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

java.lang.Short 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)                           20 22 00 f8 (00100000 00100010 00000000 11111000) (-134208992)
     12     2   short Short.value                               1
     14     2         (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total

java.lang.Byte 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)                           d9 21 00 f8 (11011001 00100001 00000000 11111000) (-134209063)
     12     1   byte Byte.value                                1
     13     3        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

java.lang.String object internals:
 OFFSET  SIZE     TYPE DESCRIPTION                               VALUE
      0     4          (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4          (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4          (object header)                           da 02 00 f8 (11011010 00000010 00000000 11111000) (-134216998)
     12     4   char[] String.value                              [x, x, x, x]
     16     4      int String.hash                               0
     20     4          (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


Process finished with exit code 0

Byte,Short,Boolean,Float,Character,Integer= 16字节

String,Double,Long=24字节

基本类型数组(16byte)

public static void main(String[] args) {
 
        int[] a1=new int[0];
        System.out.println(ClassLayout.parseInstance(a1).toPrintable());
    }

基本类型的数组会比空对象多一个4字节,用于存储长度, 数组长度每加1那么大小就加一个类型字节

public static void main(String[] args) {
 
        int[] a1=new int[10];
        System.out.println(ClassLayout.parseInstance(a1).toPrintable());
    }

容器(List Map Set)(16~48byte)

public static void main(String[] args) {
 
        List a1=new ArrayList<>();
        System.out.println(ClassLayout.parseInstance(a1).toPrintable());

        Set a2=new HashSet<>();
        System.out.println(ClassLayout.parseInstance(a2).toPrintable());

        Map a3=new HashMap<>();
        System.out.println(ClassLayout.parseInstance(a3).toPrintable());
    }

List集合(24字节): 会比对象多2个intl类型用于计算长度和个数 ,内部属于存储在Object[]

Set集合(16字节): 内部数据存储在HashMap的key中

map集合(48字节): List集合大一倍 每一个项,key(24) ,value(24),因为多一个hash值

各种数组和容器的计算规则

基本类型计算

就是类型对应的字节大小进行相加就行

基本类型数组

数组本身16+(长度*类型字节)=最终占用内存大小

int[] a=new int[10];

16+(10*4)=56(字节)

对象数组

16+(长度*4字节引用地址)+(长度*每个对象内部的大小)=最终占用内存大小

Object[] o=new Object[10];

16+(10*4)+(10*16)=816(字节)

和对象数组的计算方式一样

List = 24+(长度*4字节引用地址)+(长度*每个对象内部的大小)=最终占用内存大小

List list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
 
            list.add(new Object());
        }

24+(10 4)+(10 16)=824(字节)

计算对象

public static class Dog {
 
        int a; //4
        Boolean b; //16
        String c; //16
        List d; //24
        Map e;//48
    }

12+4+4+4+4+4=36 (因为内存对齐的原因空对象才会是16)

然后我们给对象赋值

Dog dog = new Dog();
        dog.a= 1;  
        dog.b= true;
        dog.c= "xxxxx";
        dog.d= Arrays.asList("xxxxx","xxxxx");
        dog.e= new HashMap(){
 {
 put("a","a");put("b","b");put("c","c");}};

因为基本数据类型本身就是他的大小,在上面我们已经加进去了,我们只需要算引用类型对象的大小就行了

36+16+16+(24+2 16)+(48+2 (24+24))=268字节

然后我们将对象放入到容器中

public static void main(String[] args) {
 
        ArrayList objects = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
 
            Dog dog = new Dog();
            dog.a= 1;
            dog.b= true;
            dog.c= "xxxxx";
            dog.d= Arrays.asList("xxxxx","xxxxx");
            dog.e= new HashMap(){
 {
 put("a","a");put("b","b");put("c","c");}};
            objects.add(dog);
        }
    }

24+(4*10) + (10*268)=2.744(kb)

假设有100万条数据那么

24+(4*1000000) +(1000000*268)=272000024(字节)=272(mb)

学会计算java的内存会极大地降低内存溢出的风险,以及最大化利用内存来达到最好的效率 ,建议学一下 Java-垃圾收回机制 ,因为只会计算对象大小也不行的,还需要结合JVM虚拟机的GC机制和实际需求进行计算,假设你堆大小2G你在某一时间创建了2G的对象,那么会溢出吗? 可能会,也可能不会, 主要看这些对象在gc的时候能否被收回,那么如何知道这些对象满足了收回的条件,就要研究GC机制了…书山有路勤为径,学海无涯苦作舟

原文
https://blog.csdn.net/weixin_45203607/article/details/126055516


相关推荐

C#.NET Autofac 详解(c# autoit)

简介Autofac是一个成熟的、功能丰富的.NET依赖注入(DI)容器。相比于内置容器,它额外提供:模块化注册、装饰器(Decorator)、拦截器(Interceptor)、强o的属性/方法注...

webapi 全流程(webapi怎么部署)

C#中的WebAPIMinimalApi没有控制器,普通api有控制器,MinimalApi是直达型,精简了很多中间代码,广泛适用于微服务架构MinimalApi一切都在组控制台应用程序类【Progr...

.NET外挂系列:3. 了解 harmony 中灵活的纯手工注入方式

一:背景1.讲故事上一篇我们讲到了注解特性,harmony在内部提供了20个HarmonyPatch重载方法尽可能的让大家满足业务开发,那时候我也说了,特性虽然简单粗暴,但只能解决95%...

C# 使用SemanticKernel调用本地大模型deepseek

一、先使用ollama部署好deepseek大模型。具体部署请看前面的头条使用ollama进行本地化部署deepseek大模型二、创建一个空的控制台dotnetnewconsole//添加依赖...

C#.NET 中间件详解(.net core中间件use和run)

简介中间件(Middleware)是ASP.NETCore的核心组件,用于处理HTTP请求和响应的管道机制。它是基于管道模型的轻量级、模块化设计,允许开发者在请求处理过程中插入自定义逻辑。...

IoC 自动注入:让依赖注册不再重复劳动

在ASP.NETCore中,IoC(控制反转)功能通过依赖注入(DI)实现。ASP.NETCore有一个内置的依赖注入容器,可以自动完成依赖注入。我们可以结合反射、特性或程序集扫描来实现自动...

C#.NET 依赖注入详解(c#依赖注入的三种方式)

简介在C#.NET中,依赖注入(DependencyInjection,简称DI)是一种设计模式,用于实现控制反转(InversionofControl,IoC),以降低代码耦合、提高可...

C#从零开始实现一个特性的自动注入功能

在现代软件开发中,依赖注入(DependencyInjection,DI)是实现松耦合、模块化和可测试代码的一个重要实践。C#提供了优秀的DI容器,如ASP.NETCore中自带的Micr...

C#.NET 仓储模式详解(c#仓库货物管理系统)

简介仓储模式(RepositoryPattern)是一种数据访问抽象模式,它在领域模型和数据访问层之间创建了一个隔离层,使得领域模型无需直接与数据访问逻辑交互。仓储模式的核心思想是将数据访问逻辑封装...

C#.NET 泛型详解(c# 泛型 滥用)

简介泛型(Generics)是指在类型或方法定义时使用类型参数,以实现类型安全、可重用和高性能的数据结构与算法为什么需要泛型类型安全防止“装箱/拆箱”带来的性能损耗,并在编译时检测类型错误。可重用同一...

数据分析-相关性分析(相关性 分析)

相关性分析是一种统计方法,用于衡量两个或多个变量之间的关系强度和方向。它通过计算相关系数来量化变量间的线性关系,从而帮助理解变量之间的相互影响。相关性分析常用于数据探索和假设检验,是数据分析和统计建模...

geom_smooth()函数-R语言ggplot2快速入门18

在每节,先运行以下这几行程序。library(ggplot2)library(ggpubr)library(ggtext)#用于个性化图表library(dplyr)#用于数据处理p...

规范申报易错要素解析(规范申报易错要素解析)

为什么要规范申报?规范申报是以满足海关监管、征税、统计等工作为目的,纳税义务人及其代理人依法向海关如实申报的行为,也是海关审接单环节依法监管的重要工作。企业申报的内容须符合《中华人民共和国海关进出口货...

「Eurora」海关编码归类 全球海关编码查询 关务服务

  海关编码是什么?  海关编码即HS编码,为编码协调制度的简称。  其全称为《商品名称及编码协调制度的国际公约》(InternationalConventionforHarmonizedCo...

9月1日起,河南省税务部门对豆制品加工业试行新政7类豆制品均适用投入产出法

全媒体记者杨晓川报道9月2日,记者从税务部门获悉,为减轻纳税人税收负担,完善农产品增值税进项税额抵扣机制,根据相关规定,结合我省实际情况,经广泛调查研究和征求意见,从9月1日起,我省税务部门对豆制品...