• 你创建的对象真正占了多少内存?


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

    上周写代码的时候遇到如下一个逻辑:一个10000 size的list,再创建一个list,把数据都写进来,新的list占多少内存?

    这个东西分析起来还是挺麻烦的,让我们一步步来。

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

    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”。

    • 对象头的另外一部分是klass,类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例.

    • 数组类型长度
      _mark _kclass 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 VM的自动内存管理系统要求对象起始地址必须是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

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

  • 相关阅读:
    random 模块
    re 模块
    正则表达式
    15. 3Sum
    253. Meeting Rooms II
    91. Decode Ways
    17. Letter Combinations of a Phone Number
    314. Binary Tree Vertical Order Traversal
    311. Sparse Matrix Multiplication
    311. Sparse Matrix Multiplication
  • 原文地址:https://www.cnblogs.com/lcxdever/p/10813167.html
Copyright © 2020-2023  润新知