最近在讀《深刻理解Java虛擬機》,對Java對象的內存佈局有了進一步的認識,因而腦子裏天然而然就有一個很普通的問題,就是一個Java對象到底佔用多大內存?java
在網上搜到了一篇博客講的很是好:http://yueyemaitian.iteye.com/blog/2033046,裏面提供的這個類也很是實用:數組
import java.lang.instrument.Instrumentation; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.Set; /** * 對象佔用字節大小工具類 * * @author tianmai.fh * @date 2014-03-18 11:29 */ public class SizeOfObject { static Instrumentation inst; public static void premain(String args, Instrumentation instP) { inst = instP; } /** * 直接計算當前對象佔用空間大小,包括當前類及超類的基本類型實例字段大小、<br></br> * 引用類型實例字段引用大小、實例基本類型數組總佔用空間、實例引用類型數組引用自己佔用空間大小;<br></br> * 可是不包括超類繼承下來的和當前類聲明的實例引用字段的對象自己的大小、實例引用數組引用的對象自己的大小 <br></br> * * @param obj * @return */ public static long sizeOf(Object obj) { return inst.getObjectSize(obj); } /** * 遞歸計算當前對象佔用空間總大小,包括當前類和超類的實例字段大小以及實例字段引用對象大小 * * @param objP * @return * @throws IllegalAccessException */ public static long fullSizeOf(Object objP) throws IllegalAccessException { Set<Object> visited = new HashSet<Object>(); Deque<Object> toBeQueue = new ArrayDeque<Object>(); toBeQueue.add(objP); long size = 0L; while (toBeQueue.size() > 0) { Object obj = toBeQueue.poll(); //sizeOf的時候已經計基本類型和引用的長度,包括數組 size += skipObject(visited, obj) ? 0L : sizeOf(obj); Class<?> tmpObjClass = obj.getClass(); if (tmpObjClass.isArray()) { //[I , [F 基本類型名字長度是2 if (tmpObjClass.getName().length() > 2) { for (int i = 0, len = Array.getLength(obj); i < len; i++) { Object tmp = Array.get(obj, i); if (tmp != null) { //非基本類型須要深度遍歷其對象 toBeQueue.add(Array.get(obj, i)); } } } } else { while (tmpObjClass != null) { Field[] fields = tmpObjClass.getDeclaredFields(); for (Field field : fields) { if (Modifier.isStatic(field.getModifiers()) //靜態不計 || field.getType().isPrimitive()) { //基本類型不重複計 continue; } field.setAccessible(true); Object fieldValue = field.get(obj); if (fieldValue == null) { continue; } toBeQueue.add(fieldValue); } tmpObjClass = tmpObjClass.getSuperclass(); } } } return size; } /** * String.intern的對象不計;計算過的不計,也避免死循環 * * @param visited * @param obj * @return */ static boolean skipObject(Set<Object> visited, Object obj) { if (obj instanceof String && obj == ((String) obj).intern()) { return true; } return visited.contains(obj); } }
你們能夠用這個代碼邊看邊驗證,注意的是,運行這個程序須要經過javaagent注入Instrumentation,具體能夠看原博客。我今天主要是總結下手動計算Java對象佔用字節數的基本規則,作爲基本的技能必須get√,但願能幫到和我同樣的Java菜鳥。ide
在介紹以前,簡單回顧下,Java對象的內存佈局:對象頭(Header),實例數據(Instance Data)和對齊填充(Padding),詳細的能夠看個人讀書筆記。另外:不一樣的環境結果可能有差別,我所在的環境是HotSpot虛擬機,64位Windwos。工具
下面進入正文:佈局
對象頭在32位系統上佔用8bytes,64位系統上佔用16bytes。ui
原生類型(primitive type)的內存佔用以下:spa
Primitive Type | Memory Required(bytes) |
boolean | 1 |
byte | 1 |
short | 2 |
char | 2 |
int | 4 |
float | 4 |
long | 8 |
double | 8 |
reference類型在32位系統上每一個佔用4bytes, 在64位系統上每一個佔用8bytes。指針
HotSpot的對齊方式爲8字節對齊:code
(對象頭 + 實例數據 + padding) % 8等於0且0 <= padding < 8對象
對象佔用的內存大小收到VM參數UseCompressedOops的影響。
開啓(-XX:+UseCompressedOops)對象頭大小爲12bytes(64位機器)。
static class A { int a; }
A對象佔用內存狀況:
關閉指針壓縮: 16+4=20不是8的倍數,因此+padding/4=24
開啓指針壓縮: 12+4=16已是8的倍數了,不須要再padding。
64位機器上reference類型佔用8個字節,開啓指針壓縮後佔用4個字節。
static class B2 { int b2a; Integer b2b; }
B2對象佔用內存狀況:
關閉指針壓縮: 16+4+8=28不是8的倍數,因此+padding/4=32
開啓指針壓縮: 12+4+4=20不是8的倍數,因此+padding/4=24
64位機器上,數組對象的對象頭佔用24個字節,啓用壓縮以後佔用16個字節。之因此比普通對象佔用內存可能是由於須要額外的空間存儲數組的長度。
先考慮下new Integer[0]佔用的內存大小,長度爲0,便是對象頭的大小:
未開啓壓縮:24bytes
開啓壓縮後:16bytes
接着計算new Integer[1],new Integer[2],new Integer[3]和new Integer[4]就很容易了:
未開啓壓縮:
開啓壓縮:
拿new Integer[3]來具體解釋下:
未開啓壓縮:24(對象頭)+8*3=48,不須要padding;
開啓壓縮:16(對象頭)+3*4=28,+padding/4=32,其餘依次類推。
自定義類的數組也是同樣的,好比:
static class B3 { int a; Integer b; }
new B3[3]佔用的內存大小:
未開啓壓縮:48
開啓壓縮後:32
計算複合對象佔用內存的大小其實就是運用上面幾條規則,只是麻煩點。
直接計算當前對象佔用空間大小,包括當前類及超類的基本類型實例字段大小、引用類型實例字段引用大小、實例基本類型數組總佔用空間、實例引用類型數組引用自己佔用空間大小; 可是不包括超類繼承下來的和當前類聲明的實例引用字段的對象自己的大小、實例引用數組引用的對象自己的大小。
static class B { int a; int b; } static class C { int ba; B[] as = new B[3]; C() { for (int i = 0; i < as.length; i++) { as[i] = new B(); } } }
未開啓壓縮:16(對象頭)+4(ba)+8(as引用的大小)+padding/4=32
開啓壓縮:12+4+4+padding/4=24
遞歸計算當前對象佔用空間總大小,包括當前類和超類的實例字段大小以及實例字段引用對象大小。
遞歸計算複合對象佔用的內存的時候須要注意的是:對齊填充是以每一個對象爲單位進行的,看下面這個圖就很容易明白。
如今咱們來手動計算下C對象佔用的所有內存是多少,主要是三部分構成:C對象自己的大小+數組對象的大小+B對象的大小。
未開啓壓縮:
(16 + 4 + 8+4(padding)) + (24+ 8*3) +(16+8)*3 = 152bytes
開啓壓縮:
(12 + 4 + 4 +4(padding)) + (16 + 4*3 +4(數組對象padding)) + (12+8+4(B對象padding))*3= 128bytes
你們有興趣的能夠試試。
實際工做中真正須要手動計算對象大小的場景應該不多,可是我的以爲作爲基礎知識每一個Java開發人員都應該瞭解,另外:對本身寫的代碼大概佔用多少內存,內存中是怎麼佈局的應該有一個直覺性的認識。