美文网首页程序员
Java对象大小计算

Java对象大小计算

作者: 鱼蛮子9527 | 来源:发表于2022-05-17 09:33 被阅读0次

    你创建的对象真正占了多少内存?作为程序员基本每天都在 new 对象,那么 new 出来的对象真正占用了多少内存呢?你 new 出来的对象会不会导致 OOM ?不知道大家关注过没。

    问题来源

    之前写代码的时候遇到如下一个逻辑:有一个 10000 size 的 List,需要再创建一个 List,把数据都写进来,那么这个操作会新增多少内存占用呢?这个东西分析起来还挺有意思,那么就让我们以 ArrayList 为例来分析下 new 出来的对象所占用的内存吧。

    首先我们要分析都先创建了那些对象?

    List<String> list = Lists.newArrayListWithCapacity(10000);
    

    我们通过上段代码创建了新 List,并执行了数据处理,可以看出来主要是创建了 10000 容量的 ArrayList。我们都知道 ArrayList 中存储的都是对象的引用,所以这部分操作增加的内存就是这个新的 ArrayList 需要分配的内存。

    再看下 ArrayList 的源码会发下主要就下面两个私有字段,以及 AbstractList 中的 modCount 字段。

        /**
         * The array buffer into which the elements of the ArrayList are stored.
         * The capacity of the ArrayList is the length of this array buffer. Any
         * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
         * will be expanded to DEFAULT_CAPACITY when the first element is added.
         */
        transient Object[] elementData; // non-private to simplify nested class access
    
        /**
         * The size of the ArrayList (the number of elements it contains).
         *
         * @serial
         */
        private int size;
    
    protected transient int modCount = 0;
    

    也就是说 ArrayList 需要分配实例数据就是属性:size 跟 elementData 大小,由于 elementData 是数组,所以对 ArrayList 来说也只需要分配其引用即可。最核心的内存占用就是 Objecgt[] elementData 这个数组了,在初始化 ArrayList 时候如果指定其容量会执行如下代码,其实就直接初始化了一个相应大小的数组。

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    

    对象分析

    接着我们来分析如何计算一个对象的内存占用大小。

    在 HotSpot 虚拟机中,对象在内存中存储的布局可以分为 3 块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

    对象头

    HotSpot 虚拟机的对象头包括三部分信息:

    • 第一部分 MarkWord,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,这部分数据的长度在 32 位和 64 位的虚拟机(未开启压缩指针)中分别为 32bit 和 64bit,官方称它为“MarkWord”
    • 对象头的另外一部分是 KlassWord,类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例.
    • 数组类型长度
    MarkWord KlassWord Array Length
    32bit 4 4 4
    64bit 8 8 4
    64bit+comp 8 4 4

    实例数据

    实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。

    JVM 会根据如下顺序进行字段排序,为了是节省 padding 次数

    1. doubles (8) and longs (8)
    2. ints (4) and floats (4)
    3. shorts (2) and chars (2)
    4. booleans (1) and bytes (1)
    5. references (4/8(64bit未开启压缩))
    6. repeat for sub-class fields

    可以看出来相同宽度的总是被分配在一起,在满足这个前提条件下,父类定义的变量会出现在子类之前,但如果开启了 CompactFields 参数,子类中较窄的变量也可能会插到父类变量空隙中。

    对齐填充

    第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于 HotSpot JVM 的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说,就是对象的大小必须是 8 字节的整数倍。当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

    实际计算

    了解了这些信息以后我们就可以进行上面对象的计算了,我们是在 64bit 下并开启了指针压缩情况下进行计算。

    首先计算 10000 长度的数组大小。

    head data padding total
    8 + 4 + 4 4 * 10000 0 40016

    再计算下 ArrayList 对象的大小

    head data padding total
    8 + 4 + 0 4 + 4 + 4 0 24

    那总的内存占用也就是 24 + 40016 = 40040。

    简单工具

    那么有没有简单的工具,我输入给他一个对象他就帮我计算出内存大小呢?有的,主要有两种方式一种是使用 Instrumentation,但是用起来还挺麻烦,有兴趣的同学可以自己研究下,这里主要介绍下 jol 的使用,jol 是OpenJDK 的一个工具包可以用来方便的计算对象或者类的内存占用大小,也提供 Maven GAV,可以自行去搜索引用。

    那么下面使用下 jol 工具来验证下我们的计算结果。

    执行如下代码:

    List<String> list = Lists.newArrayListWithCapacity(10000);
    System.out.println(ClassLayout.parseInstance(list).toPrintable());
    
    Field field = ArrayList.class.getDeclaredField("elementData");
    field.setAccessible(true);
    Object array = field.get(list);
    System.out.println(ClassLayout.parseInstance(array).toPrintable());
    

    输出结果如下:

    java.util.ArrayList 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)                           22 2f 00 f8 (00100010 00101111 00000000 11111000) (-134205662)
         12     4                  int AbstractList.modCount                     0
         16     4                  int ArrayList.size                            0
         20     4   java.lang.Object[] ArrayList.elementData                     [null, null, .....]
    Instance size: 24 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
    
    [Ljava.lang.Object; 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)                           f5 22 00 f8 (11110101 00100010 00000000 11111000) (-134208779)
         12     4                    (object header)                           10 27 00 00 (00010000 00100111 00000000 00000000) (10000)
         16 40000   java.lang.Object Object;.<elements>                        N/A
    Instance size: 40016 bytes
    Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
    

    跟我们之前的计算的结果一致,如果不开启指针压缩,-XX:-UseCompressedOops 那么结果如下:

    java.util.ArrayList 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)                           50 a5 8e 21 (01010000 10100101 10001110 00100001) (562996560)
         12     4                      (object header)                           02 00 00 00 (00000010 00000000 00000000 00000000) (2)
         16     4                  int AbstractList.modCount                     0
         20     4                      (alignment/padding gap)                  
         24     4                  int ArrayList.size                            0
         28     4                      (alignment/padding gap)                  
         32     8   java.lang.Object[] ArrayList.elementData                     [null, null, ......]
    Instance size: 40 bytes
    Space losses: 8 bytes internal + 0 bytes external = 8 bytes total
    
    [Ljava.lang.Object; 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)                           f0 d1 8b 21 (11110000 11010001 10001011 00100001) (562811376)
         12     4                    (object header)                           02 00 00 00 (00000010 00000000 00000000 00000000) (2)
         16     4                    (object header)                           10 27 00 00 (00010000 00100111 00000000 00000000) (10000)
         20     4                    (alignment/padding gap)                  
         24 80000   java.lang.Object Object;.<elements>                        N/A
    Instance size: 80024 bytes
    Space losses: 4 bytes internal + 0 bytes external = 4 bytes total
    

    那么,你学会了如何计算一个对象的实际内存占用了吗?

    相关文章

      网友评论

        本文标题:Java对象大小计算

        本文链接:https://www.haomeiwen.com/subject/tlggurtx.html