當前位置: 妍妍網 > 碼農

支付寶一面:一個 Java 物件到底有多大?被問傻眼了!!

2024-03-23碼農


編寫Java程式碼的時候,大多數情況下,我們很少關註一個Java物件究竟有多大(占據多少記憶體),更多的是關註業務與邏輯。但是殊不知,在我們不經意間,大量的記憶體被無形地浪費了。

1

一個Java物件到底有多大?

想要精確計算一個Java物件占用的記憶體,首先要了解Java物件的結構表示。

2

Java物件結構

一個Java物件在Heap的表示,可以分為三部份:

  • Object Header

  • class Pointer

  • Fields

  • 每個普通Java物件在堆(heap)中都有一個頭資訊(object header),頭資訊是必不可少的,記錄著物件的狀態。

    32位元與64位元占用空間不同,在32位元中:

    hash(25)+age(4)+lock(3)=32bit

    64位元中:

    unused(25+1)+hash(31)+age(4)+lock(3)=64bit

    我們知道,在Java中,一切皆物件;每個類都有一個父類, class Pointer就是當前物件父類的一個指標,在32位元系統中,這個指標為4byte;在64位元系統中,如果開啟指標壓縮(-XX:+UseCompressedOops)或者JVM堆的最大值小於32G,這個指標也是4byte,否則是8byte。

    關於欄位(Fields),這裏指的是類的例項欄位;也就是說不包括靜態欄位,因為這個欄位是共享記憶體的,只會存在一份。

    下面以32位元系統為例子,計算一下java.lang.Integer到底占用多大記憶體:

    Object Header 和 Pointer 都是固定的,4+4=8byte;再看看欄位,只有這一個,表示數值:

    /**
     * The value of the <code>Integer</code>.
     *
     * @serial
     */

    privatefinal int value;

    一個int在java中占據4byte,所以Integer的大小為4+4+4=12byte。

    這個結果對嗎?不對!還有一點沒有說:在java,物件占用的heap大小是8位元對齊的,上面的12byte沒有對齊,所以需要補位4byte。結果是16byte!

    另外,在Java中還有一種特殊的物件,陣列!沒錯,這個物件有點特殊,它比其他物件多了一個內容:長度(length)。所以我們計算陣列長度的時候,需要額外加上一個長度的欄位,即一個int的大小。

    例如:int[] arr = new int[10];

    arr的占用heap大小為:

    4(object header)+4(pointer)+4(length)+4*10(10int大小)=52byte 由於需要8位對齊,所以最終大小為`56byte`。

    3

    節約記憶體原則

    在了解了物件的記憶體使用情況後,我們可以簡單算一筆帳。一個java.lang.Integer占用16byte,而一個int占用4byte,4:1的比例!也就是說整數的類型別是基本型別記憶體的4倍!

    由此我們得出第一個節約記憶體的原則:

    1)盡量使用基本型別,而不是包裝型別。

    資料庫建表的時候欄位型別需要仔細推敲,同樣JavaBean中的內容欄位型別也需要仔細斟酌。不要吝嗇使用short,byte,boolean,如果短型別能放下數據,盡量不要使用更長的型別。

    一個long比一個int才多4byte,但是你要想,如果記憶體中有100W個long,那就白白浪費了約4MB空間,不要小看這一點點的空間浪費,因為隨便一個跑著線上套用的JVM中,物件都能達到上千萬!記憶體是節省出來的。

    2)斟酌欄位型別,在滿足容量前提下,盡量用小欄位。

    你知道一個ArrayList集合,如果裏面放了10個數位,占用多少記憶體嗎?讓我們算算:

    ArrayList中有兩個欄位:

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer.
     */

    private transient Object[] elementData;
    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */

    private int size;

    Object Header占4byte,Pointer占4byte,一個int欄位(size)占4byte,elementData陣列本身占12(4+4+4),陣列中10個Integer物件占10×16。所以整個集合空間大小為4+4+4+12+160=184byte。

    如果我們用int[]代替集合呢,12+4×10=52byte,對其後56byte。

    集合跟陣列的比例是184:56,超過3:1了!

    所以我們的第三個建議是:

    3)如果可能,盡量用陣列,少用集合。

    陣列中是可以使用基本型別的,但是集合中只能放包裝型別!

    如果實在需要使用集合,推薦一個比較節約記憶體的集合工具,fastutil。這裏麵包含了JKD集合中絕大部份的實作,而且比較省記憶體。

    4)小技

    在上面的三個原則基礎上,提供兩個小技巧。

  • 時間用long/int表示,不用Date或者String。

  • 短字串如果能窮舉或者轉換成ascii表示,可以用long或者int表示。

  • 小技巧跟具體的場景是數據有關系,可以根據實際情況進行激進最佳化節省記憶體。

    4

    總結

    效能和可讀性向來就有些矛盾,在這裏也是,為了節約記憶體,不得不進行取舍,程式碼醜陋了一些,可讀性差了一些,還好能省下一些記憶體。上面的原則在確實需要節約記憶體的時候,不妨可以試試!

    - EOF -

    推薦閱讀 點選標題可跳轉

    ·················END·················

    看完本文有收獲?請轉發分享給更多人

    關註「哪咤編程」,提升Java技能

    點贊和在看就是最大的支持 ❤️