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


    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字节效果最佳。)

    内存消耗演示

            <dependency>
                <groupId>org.openjdk.jolgroupId>
                <artifactId>jol-coreartifactId>
                <version>0.9version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    空对象(16byte)

        public static class Dog {
        }
    
        public static void main(String[] args) {
            Dog dog =new Dog();
            System.out.println(ClassLayout.parseInstance(dog).toPrintable());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    明明是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());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    引用地址(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());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述
    任何非基本类型的变量都存储的是对象的引用地址,而在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());
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92

    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());
        }
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述
    基本类型的数组会比空对象多一个4字节,用于存储长度, 数组长度每加1那么大小就加一个类型字节

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

    在这里插入图片描述

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

        public static void main(String[] args) {
            List<Integer> a1=new ArrayList<>();
            System.out.println(ClassLayout.parseInstance(a1).toPrintable());
    
            Set<Integer> a2=new HashSet<>();
            System.out.println(ClassLayout.parseInstance(a2).toPrintable());
    
            Map<String,String> a3=new HashMap<>();
            System.out.println(ClassLayout.parseInstance(a3).toPrintable());
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

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

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

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

    基本类型计算

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

    基本类型数组

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

    int[] a=new int[10];
    
    • 1

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

    对象数组

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

    Object[] o=new Object[10];
    
    • 1

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

    集合

    和对象数组的计算方式一样
    List = 24+(长度*4字节引用地址)+(长度*每个对象内部的大小)=最终占用内存大小

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

    24+(104)+(1016)=824(字节)

    计算对象

        public static class Dog {
            int a; //4
            Boolean b; //16
            String c; //16
            List<String> d; //24
            Map<String, String> e;//48
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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");}};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    因为基本数据类型本身就是他的大小,在上面我们已经加进去了,我们只需要算引用类型对象的大小就行了
    36+16+16+(24+216)+(48+2(24+24))=268字节

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

        public static void main(String[] args) {
            ArrayList<Object> 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);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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机制了…书山有路勤为径,学海无涯苦作舟

    在这里插入图片描述

    点赞 -收藏-关注-便于以后复习和收到最新内容
    有其他问题在评论区讨论-或者私信我-收到会在第一时间回复
    在本博客学习的技术不得以任何方式直接或者间接的从事违反中华人民共和国法律,内容仅供学习、交流与参考
    免责声明:本文部分素材来源于网络,版权归原创者所有,如存在文章/图片/音视频等使用不当的情况,请随时私信联系我、以迅速采取适当措施,避免给双方造成不必要的经济损失。
    感谢,配合,希望我的努力对你有帮助^_^
  • 相关阅读:
    PHP 安装64位oracle客户端
    吊灯需要做哪些检测认证
    上交所实时行情文件汇总
    调用链监控工具之CAT上
    什么是 Redis?
    基本元器件 - 二极管
    JAVA学习第2步——项目创建和导包
    数据结构与算法-Btree&B+Tree
    使用Python开源库Couler编写和提交Argo Workflow工作流
    【mysql】navicat工具上显示到的表的行数和实际的行数不一致
  • 原文地址:https://blog.csdn.net/weixin_45203607/article/details/126055516