1 對象內存大小度量
原文:http://www.liaohuqiu.net/cn/posts/caculate-object-size-in-java/補充了文中的細節,做了一些訂正,加粗斜體是補充和訂正
在做內存優化時,需要知道每個對象佔用的內存的大小,
一個實例化的對象在內存中需要存儲的信息包括:
1. 對象的頭部(對象的GC信息,hash值,類定義引用等)
2. 對象的成員變量:包括基本數據類型和引用。如成員變量是一個引用,引用了其他對象,被引用的對象內存另外計算。
如下一個簡單的類的定義:
class MyClass {
int a;
Object object;
}
1.1 實例化一個對象
MyClass myClass = new MyClass();
對象大小分爲:
1.1.1自身的大小(Shadow heap size):
直接計算當前對象佔用空間大小,包括當前類及超類的基本類型實例字段大小、引用類型實例字段引用大小、實例基本類型數組總佔用空間、實例引用類型數組引用本身佔用空間大小
1.1.2所引用的對象的大小(Retained heap size):
myClass實例創建出來之後,在內存中所佔的大小就是myClass自身大小(Shadow heap size)。包括類的頭部大小以及一個int的大小和一個引用的大小。
myClass 中object 成員變量是一個對象引用,這個被引用的對象也佔一定大小。myClass實例所維護的引用的對象所佔的大小,稱爲myClass實例的Retained heap size。
本文討論的是對象自身的大小,即Shadow heap size。Retained heap size遞歸計算即可得。
2度量工具
對象大小的計算可用java.lang.instrument.Instrumentation 或者 dump內存之後用memory analyzer分析。這是一份示例代碼java-object-size
2.1基本數據類型大小
基本數據類型大小如下: From WIKI
type |
size(bits) |
bytes |
boolean |
8 |
1 |
byte |
8 |
1 |
char |
16 |
2 |
short |
16 |
2 |
int |
32 |
4 |
long |
64 |
8 |
float |
32 |
4 |
double |
64 |
8 |
2.2引用的大小
在32位的JVM上,一個對象引用佔用4個字節;在64位上,佔用8個字節。通過 java -d64 -version可確定是否是64位的JVM。
使用8個字節是爲了能夠管理大於4G的內存,如果你的程序不需要訪問大於4G的內存,
可通過-XX:+UseCompressedOops選項,開啓指針壓縮。從Java 1.6.0_23起,這個選項默認是開的。可通過jinfo -flag UseCompressedOops <pid>查看。
localhost:~ srain$ jinfo -flag UseCompressedOops 13133
-XX:+UseCompressedOops
2.3對象頭部的大小
對象頭,結構如下(來源):
+------------------+------------------+------------------+---------------.
| mark word | klass pointer | array size (opt) | padding |
+------------------+------------------+-------------------+---------------'
每個對象都有一個mark work頭部,以及一個引用,指向類的信息。在32位JVM上,markword 4個字節,整個頭部有8字節大小。
array size(opt)在對象爲數組的時候啓用,4字節(4byte)長度。JVM規定對象頭(Object Header)長度爲2個字(word),在32bit JVM中,一個word長度爲4byte,64bit JVM中,長度爲8byte
在未開啓UseCompressedOops的64位JVM上,對象頭有16字節大小。
在開啓UseCompressedOops的64位機器上,引用(klass pointer)成了4字節,一共12字節。按照8位對齊,實際佔用16字節。
java在64bit模式下開啓指針壓縮,比32bit模式下,頭部會大4byte(_mark區域變成8byte,_class區域被壓縮),如果沒有開啓指針壓縮,頭部會大8byte(_mark和_class都會變成8byte),jdk1.6推出參數-XX:+UseCompressedOops,在32G內存一下默認會自動打開這個參數,如下:
[Nathan@laptop ~]$ java -Xmx31g -XX:+PrintFlagsFinal |grep Compress
bool SpecialStringCompress = true {product}
bool UseCompressedOops = true {lp64_product}
bool UseCompressedStrings = false {product}
[Nathan@laptop ~]$ java -Xmx32g -XX:+PrintFlagsFinal |grep Compress
bool SpecialStringCompress = true {product}
bool UseCompressedOops = false {lp64_product}
bool UseCompressedStrings = false {product}
3對象的內存佈局
1. 每個對象的內存佔用按8字節對齊
2. 空對象和類實例成員變量
空對象,指的非inner-class,沒有實例屬性的類。Object 類或者直接繼承Object 沒有添加任何實例成員的類,即:new Object() 。
空對象的不包含任何成員變量,其大小即對象頭大小:
o 在32位JVM上,佔用8字節;
o 在開啓UseCompressedOops的64位JVM上,12
+ 4 = 16;
o 訂正:在開啓UseCompressedOops的64位JVM上,object header長度爲12(mark word: 8, kclass pointer: 4),padding爲4,對齊後的長度爲16;
o 在未開啓UseCompressedOops的64位JVM上,16
+ 4 = 20; 對齊後爲24。
o 訂正:在未開啓UseCompressedOops的64位JVM上,長度16(mark word:8 ,klass pointer 8)。
3. 對象實例成員重排序
實例成員變量緊隨對象頭。每個成員變量都儘量使本身的大小在內存中儘量對齊。
比如int按4位對齊,long按8位對齊。爲了內存緊湊,實例成員在內存中的排列和聲明的順序可能不一致,實際會按以下順序排序:
1. doubles andlongs
2. ints and floats
3. shorts andchars
4. booleans andbytes
5. references
這樣做可儘量節省空間。
如:
classMyClass{
byte a;
int c;
boolean d;
long e;
Object f;
}
未重排之前:
32 bit 64bit +UseCompressedOops
[HEADER: 12 bytes] 8 [HEADER: 12 bytes] 12
[a: 1 byte ] 9 [a: 1 byte ] 13
[padding: 3 bytes] 12 [padding: 3 bytes] 16
[c: 4 bytes] 16 [c: 4 bytes] 20
[d: 1 byte ] 17 [d: 1 byte ] 21
[padding: 7 bytes] 24 [padding: 3 bytes] 24
[e: 8 bytes] 32 [e: 8 bytes] 32
[f: 4 bytes] 36 [f: 4 bytes] 36
[padding: 4 bytes] 40 [padding: 4 bytes] 40
重新排列之後:
32 bit 64bit +UseCompressedOops
[HEADER: 8 bytes] 8 [HEADER: 12 bytes] 12
[e: 8 bytes] 16 [e: 8 bytes] 20
[c: 4 bytes] 20 [c: 4 bytes] 24
[a: 1 byte ] 21 [a: 1 byte ] 25
[d: 1 byte ] 22 [d: 1 byte ] 26
[padding: 2 bytes] 24 [padding: 2 bytes] 28
[f: 4 bytes] 28 [f: 4 bytes] 32
[padding: 4 bytes] 32
4. 父類和子類的實例成員
父類和子類的成員變量分開存放,先是父類的實例成員。父類實例成員變量結束之後,按4位對齊,隨後接着子類實例成員變量。
classA{
byte a;
}
classBextends A{
byte b;
}
內存結構如下:
32 bit 64bit +UseCompressedOops
[HEADER: 8 bytes] 8 [HEADER: 12 bytes] 12
[a: 1 byte ] 9 [a: 1 byte ] 13
[padding: 3 bytes] 12 [padding: 3 bytes] 16
[b: 1 byte ] 13 [b: 1 byte ] 17
[padding: 3 bytes] 16 [padding: 7 bytes] 24
如果子類首個成員變量是long或者double等8字節數據類型,而父類結束時沒有8位對齊。會把子類的小於8字節的實例成員先排列,直到能8字節對齊。
class A {
byte a;
}
class B extends A{
long b;
short c;
byte d;
}
內存結構如下:
32 bit 64bit +UseCompressedOops
[HEADER: 8 bytes] 8 [HEADER: 8 bytes] 12
[a: 1 byte ] 9 [a: 1 byte ] 13
[padding: 3 bytes] 12 [padding: 3 bytes] 16
[c: 2 bytes] 14 [b: 8 bytes] 24
[d: 1 byte ] 15 [c: 4 byte ] 28
[padding: 1 byte ] 16 [d: 1 byte ] 29
[b: 8 bytes] 24 [padding: 3 bytes] 32
上面的示例中,在32位的JVM上,B的2個實例成員c, d被提前了。
5. 非靜態的內部類,有一個隱藏的對外部類的引用。
3.1數組的內存佔用大小
數組也是對象,故有對象的頭部,另外數組還有一個記錄數組長度的int類型,隨後是每一個數組的元素:基本數據類型或者引用。8字節對齊。
32 位的機器上
byte[0] 8字節的對象頭部,4字節的int長度, 12字節,對齊後是16字節,實際 byte[0] ~byte[4]都是16字節(每個byte的長度爲1byte,byte[0]-byte[4]佔用padding長度)。
64 位+UseCompressedOops
byte[0] 是16字節大小(對象頭:mark word: 8byte, kclasspointer: 4byte, array sizeof: 4byte),byte[1] ~byte[8] 24字節大小。
64 位-UseCompressedOops
byte[0], 16字節頭部,4字節的int長度信息,20字節,對齊後 24 字節。byte[0] ~ byte[4] 都是24字節。
計算一個數組對象的公式(-XX:UseCompressedOops)
The general rules forcomputing the size of an object on JVM are :
32 bit
Arrays of boolean, byte, char, short, int: 2 * 4 (Object header)+ 4 (length-field) + sizeof(primitiveType) * length -> align result up to amultiple of 8
Arrays of objects: 2 * 4 (Object header) + 4 (length-field) + 4(objectreference length) * array length -> align result up to a multiple of 8
Arrays of longs and doubles: 2 * 4 (Object header) + 4 (length-field) + 4 (deadspace due to alignment restrictions) + 8 * length
java.lang.Object: 2 * 4 (Object header)
other objects: sizeofSuperClass + 8 * nrOfLongAndDoubleFields + 4 *nrOfIntFloatAndObjectFields + 2 * nrOfShortAndCharFields + 1 *nrOfByteAndBooleanFields -> align result up to a multiple of 8
64 bit
Arrays of boolean, byte, char, short, int: 2 * 8 (Object header)+ 4 (length-field) + sizeof(primitiveType) * length -> align result up to amultiple of 8
Arrays of objects: 2 * 8 (Object header) + 4 (length-field) + 4 (dead space dueto alignment restrictions) + 8 * length
Arrays of longs and doubles: 2 * 8 (Object header) + 4 (length-field) + 4 (deadspace due to alignment restrictions) + 8 * length
java.lang.Object: 2 * 8 (Object header)
other objects: sizeofSuperClass + 8 * nrOfLongDoubleAndObjectFields + 4 +nrOfntAndFloatFields + 2 * nrOfShortAndCharFields + 1 *nrOfByteAndBooleanFields -> align result up to a multiple of 8
Note thatan object might have unused space due to alignment at every inheritance level(e.g. imagine a class A with just a byte field and class B has A as it'ssuperclass and declares a byte field itself -> 14 bytes 'wasted on 64 bitsystem).
Inpractice 64 bit needs 30 to 50% more memory due to references being twice aslarge.
3.2字符串大小
Field |
Type |
64 bit -UseCompressedOops |
64 bit +UseCompressedOops |
32 bit |
HEADER |
無 |
16 |
12 |
8 |
value |
char[] |
8(引用) |
4 |
4 |
offset |
int |
4 |
4 |
4 |
count |
int |
4 |
4 |
4 |
hash |
int |
4 |
4 |
4 |
PADDING |
無 |
4 |
4 |
0 |
TOTAL |
無 |
40 |
32 |
24 |
不計算value引用的Retained heap size,字符串本身就需要 24 ~ 40字節大小。
一個newString("a");+UseCompressedOops這個對象的空間大小爲:12字節頭部+4*4 = 28字節對齊到32字節,然後value所指向的char數組頭部比普通對象多4個byte來存放長度,12+4+2byte的字符=18,總大小爲28+18=46byte,對齊後就是48byte,其實即使你new String()也會佔這麼大的空間,28+16=44byte,因爲有對齊,如果字符的長度是8個,那麼就是12+4+16=32,也就是有64byte;
如果不開啓指針壓縮再算算:頭部變成16byte + 4*3個int數據 + 8(1個指針) = 36對齊到40byte,對應的char數組的頭部變成16+4 + 2 = 22對齊到24byte,40+24=64byte,也就是隻有一個字符或者0個字符都會對齊到64byte,所以,你懂的,參數該怎麼調,代碼該怎麼寫,如果長度爲8個字符的那麼後面部分就會變成16+4+16=36對齊到40byte,40+40=80byte,也就是說,拋開其他的引用空間(比如通過數組或集合類引用),如果你有10來個String,每個大小就裝8個字符,就會有1K的大小
4參考資料
http://kohlerm.blogspot.com/2008/12/how-much-memory-is-used-by-my-java.html
http://www.liaohuqiu.net/cn/posts/caculate-object-size-in-java/
http://blog.csdn.net/xieyuooo/article/details/7068216
http://yueyemaitian.iteye.com/blog/2033046
http://www.cnblogs.com/zhanjindong/p/3757767.html