Java對象內存結構

轉自http://www.importnew.com/1305.html

我們接下來的討論也會基於32位的Sun公司的JVM。下面我介紹一些規則來輔助解釋JVM如何組織對象在內存中的佈局的。

沒有實例屬性的類的內存佈局

在Sun JVM中,(除了數組之外的)對象都有兩個機器字(words)的頭部。第一個字中包含這個對象的標示哈希碼以及其他一些類似鎖狀態和等標識信息,第二個字中包含一個指向對象的類的引用。另外,任何對象都是8個字節爲粒度進行對齊的。這就是對象內存佈局的第一個規則:

規則1:任何對象都是8個字節爲粒度進行對齊的。

比如,如果調用new Object(),由於Object類並沒有其他沒有其他可存儲的成員,那麼僅僅使用堆中的8個字節來保存兩個字的頭部即可。

繼承了Object的類的內存佈局

除了上面所說的8個字節的頭部,類屬性緊隨其後。屬性通常根據其大小來排列。例如,整型(int)以4個字節爲單位對齊,長整型(long)以8個字節爲單位對齊。這裏是出於性能考慮而這麼設計的:通常情況下,如果數據以4字節爲單位對齊,那麼從內存中讀4字節的數據並寫入到處理器的4字節寄存器是性價比更高的。

爲了節省內存,Sun VM並沒有按照屬性聲明時的順序來進行內存佈局。實際上,屬性在內存中按照下面的順序來組織:

1. 雙精度型(doubles)和長整型(longs)

2. 整型(ints)和浮點型(floats)

3. 短整型(shorts)和字符型(chars)

4. 布爾型(booleans)和字節型(bytes)

5. 引用類型(references)

內存使用率會通過這個機制得到優化。例如,如下聲明一個類:

class MyClass {
 
       byte a;
 
       int c;
 
       boolean d;
 
       long e;
 
       Object f;          
 
}
如果JVM並沒有打亂屬性的聲明順序,其對象內存佈局將會是下面這個樣子:

[HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[c:       4 bytes] 16
[d:       1 byte ] 17
[padding: 7 bytes] 24
[e:       8 bytes] 32
[f:       4 bytes] 36
[padding: 4 bytes] 40

此時,用於佔位的14個字節是浪費的,這個對象一共使用了40個字節的內存空間。但是,如果用上面的規則對這些對象重新排序,其內存結果會變成下面這個樣子:

[HEADER:  8 bytes]  8
[e:       8 bytes] 16
[c:       4 bytes] 20
[a:       1 byte ] 21
[d:       1 byte ] 22
[padding: 2 bytes] 24
[f:       4 bytes] 28
[padding: 4 bytes] 32

這次,用於佔位的只有6個字節,這個對象使用了32個字節的內存空間。

因此,對象內存佈局的第二個規則是:

規則2:類屬性按照如下優先級進行排列:長整型和雙精度類型;整型和浮點型;字符和短整型;字節類型和布爾類型,最後是引用類型。這些屬性都按照各自的單位對齊。

現在我們知道如何計算一個繼承了Object的類的實例的內存大小了。下面這個例子用來做下練習: java.lang.Boolean。這是其內存佈局:

[HEADER:  8 bytes]  8
[value:   1 byte ]  9
[padding: 7 bytes] 16
Boolean類的實例佔用16個字節的內存!驚訝吧?(別忘了最後用來佔位的7個字節)。

繼承其他類的子類的內存佈局

JVM所遵守的下面3個規則用來組織有父類的類的成員。對象內存佈局的規則3如下:

規則3:不同類繼承關係中的成員不能混合排列。首先按照規則2處理父類中的成員,接着纔是子類的成員。

舉例如下:

class A {
   long a;
   int b;
   int c;
}
 
class B extends A {
   long d;
}


類B的實例在內存中的存儲如下:

[HEADER:  8 bytes]  8
[a:       8 bytes] 16
[b:       4 bytes] 20
[c:       4 bytes] 24
[d:       8 bytes] 32
如果父類中的成員的大小無法滿足4個字節這個基本單位,那麼下一條規則就會起作用:

規則4:當父類中最後一個成員和子類第一個成員的間隔如果不夠4個字節的話,就必須擴展到4個字節的基本單位。

舉例如下:

class A {
   byte a;
}
 
class B {
   byte b;
}
[HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[b:       1 byte ] 13
[padding: 3 bytes] 16

注意到成員a被擴充了3個字節以保證和成員b之間的間隔是4個字節。這個空間不能被類B使用,因此被浪費了。

最後一條規則在下面情況下用來節省一些空間:如果子類成員是長整型或雙精度類型,並且父類並沒有用完8個字節。

規則5:如果子類第一個成員是一個雙精度或者長整型,並且父類並沒有用完8個字節,JVM會破壞規則2,按照整形(int),短整型(short),字節型(byte),引用類型(reference)的順序,向未填滿的空間填充。

舉例如下:

class A {
  byte a;
}
 
class B {
  long b;
  short c;  
  byte d;
}
其內存佈局如下:

[HEADER:  8 bytes]  8
[a:       1 byte ]  9
[padding: 3 bytes] 12
[c:       2 bytes] 14
[d:       1 byte ] 15
[padding: 1 byte ] 16
[b:       8 bytes] 24

在第12字節處,類A“結束”的地方,JVM沒有遵守規則2,而是在長整型之前插入一個短整型和一個字節型成員,這樣可以避免浪費3個字節。

數組的內存佈局

數組有一個額外的頭部成員,用來存放“長度”變量。數組元素以及數組本身,跟其他常規對象同樣,都需要遵守8個字節的邊界規則。

下面是一個有3個元素的字節數組的內存佈局:

[HEADER:  12 bytes] 12
[[0]:      1 byte ] 13
[[1]:      1 byte ] 14
[[2]:      1 byte ] 15
[padding:  1 byte ] 16
下面是一個有3個元素的長整型數字的內存佈局:

[HEADER:  12 bytes] 12
[padding:  4 bytes] 16
[[0]:      8 bytes] 24
[[1]:      8 bytes] 32
[[2]:      8 bytes] 40

內部類的內存佈局

非靜態內部類(Non-static inner classes)有一個額外的“隱藏”成員,這個成員是一個指向外部類的引用變量。這個成員是一個普通引用,因此遵守引用內存佈局的規則。內部類因此有4個字節的額外開銷。











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章