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

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

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

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


相关推荐

程序员请收好:10个非常有用的 Visual Studio Code 插件

一个插件列表,可以让你的程序员生活变得轻松许多。作者|Daan译者|Elle出品|CSDN(ID:CSDNnews)以下为译文:无论你是经验丰富的开发人员还是刚刚开始第一份工作的初级开发人...

PADS在WIN10系统中菜单显示不全的解决方法

决定由AD转PADS,打开发现菜单显示不正常,如下图所示:这个是由于系统的默认字体不合适导致,修改一下系统默认字体即可,修改方法如下:打开开始菜单-->所有程序-->Windows系统--...

一文讲解Web前端开发基础环境配置

先从基本的HTML语言开始学习。一个网页的所有内容都是基于HTML,为了学好HTML,不使用任何集成工具,而用一个文本编辑器,直接从最简单的HTML开始编写HTML。先在网上下载notepad++文...

TCP/IP协议栈在Linux内核中的运行时序分析

本文主要是讲解TCP/IP协议栈在Linux内核中的运行时序,文章较长,里面有配套的视频讲解,建议收藏观看。1Linux概述  1.1Linux操作系统架构简介Linux操作系统总体上由Linux...

从 Angular Route 中提前获取数据

#头条创作挑战赛#介绍提前获取意味着在数据呈现在屏幕之前获取到数据。本文中,你将学到,在路由更改前怎么获取到数据。通过本文,你将学会使用resolver,在AngularApp中应用re...

边做游戏边划水: 基于浅水方程的水面交互、河道交互模拟方法

以下文章来源于腾讯游戏学堂,作者Byreave篇一:基于浅水方程的水面交互本文主要介绍一种基于浅水方程的水体交互算法,在基本保持水体交互效果的前提下,实现了一种极简的水面模拟和物体交互方法。真实感的...

Nacos介绍及使用

一、Nacos介绍Nacos是SpringCloudAlibaba架构中最重要的组件。Nacos是一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台,提供注册中心、配置中心和动态DNS...

Spring 中@Autowired,@Resource,@Inject 注解实现原理

使用案例前置条件:现在有一个Vehicle接口,它有两个实现类Bus和Car,现在还有一个类VehicleService需要注入一个Vehicle类型的Bean:publicinte...

一文带你搞懂Vue3 底层源码

作者:妹红大大转发链接:https://mp.weixin.qq.com/s/D_PRIMAD6i225Pn-a_lzPA前言vue3出来有一段时间了。今天正式开始记录一下梗vue3.0.0-be...

一线开发大牛带你深度解析探讨模板解释器,解释器的生成

解释器生成解释器的机器代码片段都是在TemplateInterpreterGenerator::generate_all()中生成的,下面将分小节详细展示该函数的具体细节,以及解释器某个组件的机器代码...

Nacos源码—9.Nacos升级gRPC分析五

大纲10.gRPC客户端初始化分析11.gRPC客户端的心跳机制(健康检查)12.gRPC服务端如何处理客户端的建立连接请求13.gRPC服务端如何映射各种请求与对应的Handler处理类14.gRP...

聊聊Spring AI的Tool Calling

序本文主要研究一下SpringAI的ToolCallingToolCallbackorg/springframework/ai/tool/ToolCallback.javapublicinter...

「云原生」Containerd ctr,crictl 和 nerdctl 命令介绍与实战操作

一、概述作为接替Docker运行时的Containerd在早在Kubernetes1.7时就能直接与Kubelet集成使用,只是大部分时候我们因熟悉Docker,在部署集群时采用了默认的dockers...

在MySQL登录时出现Access denied for user ~~ (using password: YES)

Windows~~~在MySQL登录时出现Accessdeniedforuser‘root‘@‘localhost‘(usingpassword:YES),并修改MySQL密码目录适用...

mysql 8.0多实例批量部署script

背景最近一个项目上,客户需要把阿里云的rdsformysql数据库同步至线下,用作数据的灾备,需要在线下的服务器上部署mysql8.0多实例,为了加快部署的速度,写了一个脚本。解决方案#!/bi...