你創建的對象真正占了多少內存?


你創建的對象真正占了多少內存?作為程序員基本每天都在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

那么你學會了如何計算一個對象的占用內存大小了嗎?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM