編寫Java程式碼的時候,大多數情況下,我們很少關註一個Java物件究竟有多大(佔據多少記憶體),更多的是關註業務與邏輯。但是殊不知,在我們不經意間,大量的記憶體被無形地浪費了。
一個Java物件到底有多大?
想要精確計算一個Java物件佔用的記憶體,首先要瞭解Java物件的結構表示。
Java物件結構
一個Java物件在Heap的表示,可以分為三部分:
- Object Header
- Class Pointer
- Fields
每個普通Java物件在堆(heap)中都有一個頭資訊(object essay-header),頭資訊是必不可少的,記錄著物件的狀態。
32位與64位佔用空間不同,在32位中:
hash(25)+age(4)+lock(3)=32bit
64位中:
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;再看看欄位,只有這一個,表示數值:
/**
* The value of the
Integer
.*
* @serial
*/
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中有兩個欄位:
/**
* 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集合中絕大部分的實現,而且比較省記憶體。
小技巧
在上面的三個原則基礎上,提供兩個小技巧。
- 時間用long/int表示,不用Date或者String。
- 短字串如果能窮舉或者轉換成ascii表示,可以用long或者int表示。
註意:小技巧跟具體的場景資料有關係,可以根據實際情況進行激進最佳化節省記憶體。
總結
效能和可讀性向來就有些矛盾,在這裡也是,為了節約記憶體,不得不進行取捨,程式碼醜陋了一些,可讀性差了一些,還好能省下一些記憶體。上面的原則在 確實需要節約記憶體的時候 ,不妨可以試試!
朋友會在“發現-看一看”看到你“在看”的內容