歡迎光臨
每天分享高質量文章

一個Java物件到底有多大?

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

一個Java物件到底有多大?

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

Java物件結構

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

  • Object Header
  • Class Pointer
  • Fields

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

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

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

64位中:

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

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

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

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

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

  1. /**
  2.  * The value of the Integer.
  3.  *
  4.  * @serial
  5.  */
  6. private final 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 essay-header)+4(pointer)+4(length)+4*10(10個int大小)=52byte 由於需要8位對齊,所以最終大小為 56byte

節約記憶體原則

在瞭解了物件的記憶體使用情況後,我們可以簡單算一筆帳。一個 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中有兩個欄位:

  1. /**
  2.  * The array buffer into which the elements of the ArrayList are stored.
  3.  * The capacity of the ArrayList is the length of this array buffer.
  4.  */
  5. private transient Object[] elementData;
  6. /**
  7.  * The size of the ArrayList (the number of elements it contains).
  8.  *
  9.  * @serial
  10.  */
  11. 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集合中絕大部分的實現,而且比較省記憶體。

小技巧

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

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

註意:小技巧跟具體的場景資料有關係,可以根據實際情況進行激進優化節省記憶體。

總結

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

赞(0)

分享創造快樂