一直以來,對java對象大小的概念停留在基礎數據類型,好比byte佔1字節,int佔4字節,long佔8字節等,可是一個對象包含的內存空間確定不僅有這些。java
假設有類A和B,當new A()或者new B()後,實際佔用的java內存是多大呢?下面就對此進行詳細分析。apache
static class A{ String s = new String(); int i = 0; } static class B{ String s; int i; }
如圖1,java對象在內存中佔用的空間分爲3類, 1. 對象頭(Header); 2. 實例數據(Instance Data); 3. 對齊填充(Padding)。而咱們常說的基礎數據類型大小主要是指第二類實例數據。數組
圖1jvm
HotSpot虛擬機的對象頭包括兩部分信息:spa
markword和klass 。第一部分markword,用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等。 另一部分是klass類型指針,即對象指向它的類元數據的指針,虛擬機經過這個指針來肯定這個對象是哪一個類的實例。線程
若是對象是一個數組, 那在對象頭中還必須有一塊數據用於記錄數組長度,也就是一個int類型的對象,佔4字節。debug
1. 在32位系統下,存放Class指針的空間大小是4字節,MarkWord是4字節,對象頭爲8字節。指針
2. 在64位系統下,存放Class指針的空間大小是8字節,MarkWord是8字節,對象頭爲16字節。code
3. 在64位開啓指針壓縮的狀況下 -XX:+UseCompressedOops,存放Class指針的空間大小是4字節,MarkWord是8字節,對象頭爲12字節。對象
4. 若是對象是數組,那麼額外增長4個字節
實例數據部分是對象真正存儲的有效信息,也是在程序代碼中所定義的各類類型的字段內容。不管是從父類繼承下來的,仍是在子類中定義的,都須要記錄起來。
最後一塊對齊填充空間並非必然存在的,也沒有特別的含義,它僅僅起着佔位符的做用。這是因爲HotSpot VM的自動內存管理系統要求對象起始地址必須是8字節的整數倍,換句話說,就是對象的大小必須是8字節的整數倍。
1. 基於JDK1.8
JDK1.8有一個類`jdk.nashorn.internal.ir.debug.ObjectSizeCalculator`能夠評估出對象的大小
// 直接調用靜態方法便可使用
ObjectSizeCalculator.getObjectSize(obj)
2. spark庫
spark庫中有一個類`org.apache.spark.util.SizeEstimator`
// 直接調用靜態方法便可使用
SizeEstimator.estimate(obj)
3. 基於JDK1.5的Instrumentation
// 須要編譯成jar調用,沒有前者方便
分析完對象的組成結構後,再回頭來看那個問題
// 對象A: 對象頭12B + 內部對象s引用 4B + 內部對象i 基礎類型int 4B + 對齊 4B = 24B // 內部對象s 對象頭12B + 2個內部的int類型8B + 內部的char[]引用 4B + 對齊0B = 24B // 內部對象str的內部對象char數組 對象頭12B + 數組長度4B + 對齊0B = 16B // 總: 對象A 24+ 內部對象s 24B + 內部對象s的內部對象char數組 16B =64B class A { String s = new String(); int i = 0; } // 對象B:對象頭12B + 內部對象s引用 4B + 內部對象i 基礎類型int 4B + 對齊 4B = 24B // s沒有被分配堆內存空間 // 總: 對象B 24B class B { String s; int i = 0; }
對象在jvm中不是徹底連續的,這是因爲GC的緣由,總會出現散亂的內存。這就致使了jvm必須爲每一個對象分配一段內存空間來存儲其引用的指針,再結合對象的其餘必須的元數據,使得對象在持有真實數據的基礎上還須要維護額外的數據。
在寫java代碼須要當心這些jvm內存陷阱。
// stackoverflow給出的幾種計算對象大小方法