一個(gè)Java對(duì)象到底占多大內(nèi)存?
最近在讀《深入理解Java虛擬機(jī)》,對(duì)Java對(duì)象的內(nèi)存布局有了進(jìn)一步的認(rèn)識(shí),于是腦子里自然而然就有一個(gè)很普通的問(wèn)題,就是一個(gè)Java對(duì)象到底占用多大內(nèi)存?
在網(wǎng)上搜到了一篇博客講的非常好:http://yueyemaitian.iteye.com/blog/2033046,里面提供的這個(gè)類也非常實(shí)用:
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; /** * 對(duì)象占用字節(jié)大小工具類 * * @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; }/** * 直接計(jì)算當(dāng)前對(duì)象占用空間大小,包括當(dāng)前類及超類的基本類型實(shí)例字段大小、<br></br> * 引用類型實(shí)例字段引用大小、實(shí)例基本類型數(shù)組總占用空間、實(shí)例引用類型數(shù)組引用本身占用空間大小;<br></br> * 但是不包括超類繼承下來(lái)的和當(dāng)前類聲明的實(shí)例引用字段的對(duì)象本身的大小、實(shí)例引用數(shù)組引用的對(duì)象本身的大小 <br></br> * * @param obj * @return */ public static long sizeOf(Object obj) { return inst.getObjectSize(obj); }/** * 遞歸計(jì)算當(dāng)前對(duì)象占用空間總大小,包括當(dāng)前類和超類的實(shí)例字段大小以及實(shí)例字段引用對(duì)象大小 * * @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的時(shí)候已經(jīng)計(jì)基本類型和引用的長(zhǎng)度,包括數(shù)組 size += skipObject(visited, obj) ? 0L : sizeOf(obj); Class<?> tmpObjClass = obj.getClass(); if (tmpObjClass.isArray()) { //[I , [F 基本類型名字長(zhǎng)度是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) { //非基本類型需要深度遍歷其對(duì)象 toBeQueue.add(Array.get(obj, i)); } } } } else { while (tmpObjClass != null) { Field[] fields = tmpObjClass.getDeclaredFields(); for (Field field : fields) { if (Modifier.isStatic(field.getModifiers()) //靜態(tài)不計(jì) || field.getType().isPrimitive()) { //基本類型不重復(fù)計(jì) continue; } field.setAccessible(true); Object fieldValue = field.get(obj); if (fieldValue == null) { continue; } toBeQueue.add(fieldValue); } tmpObjClass = tmpObjClass.getSuperclass(); } } } return size; }/** * String.intern的對(duì)象不計(jì);計(jì)算過(guò)的不計(jì),也避免死循環(huán) * * @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); } }
大家可以用這個(gè)代碼邊看邊驗(yàn)證,注意的是,運(yùn)行這個(gè)程序需要通過(guò)javaagent注入Instrumentation,具體可以看原博客。我今天主要是總結(jié)下手動(dòng)計(jì)算Java對(duì)象占用字節(jié)數(shù)的基本規(guī)則,做為基本的技能必須get√,希望能幫到和我一樣的Java菜鳥。
在介紹之前,簡(jiǎn)單回顧下,Java對(duì)象的內(nèi)存布局:對(duì)象頭(Header),實(shí)例數(shù)據(jù)(Instance Data)和對(duì)齊填充(Padding),詳細(xì)的可以看我的讀書筆記。另外:不同的環(huán)境結(jié)果可能有差異,我所在的環(huán)境是HotSpot虛擬機(jī),64位Windwos。
下面進(jìn)入正文:
對(duì)象頭對(duì)象頭在32位系統(tǒng)上占用8bytes,64位系統(tǒng)上占用16bytes。
原生類型(primitive type)的內(nèi)存占用如下:
Primitive TypeMemory Required(bytes)boolean1byte1short2char2int4float4long8double8reference類型在32位系統(tǒng)上每個(gè)占用4bytes, 在64位系統(tǒng)上每個(gè)占用8bytes。
對(duì)齊填充HotSpot的對(duì)齊方式為8字節(jié)對(duì)齊:
(對(duì)象頭 + 實(shí)例數(shù)據(jù) + padding) % 8等于0且0 <= padding < 8
指針壓縮對(duì)象占用的內(nèi)存大小收到VM參數(shù)UseCompressedOops的影響。
1)對(duì)對(duì)象頭的影響
開啟(-XX:+UseCompressedOops)對(duì)象頭大小為12bytes(64位機(jī)器)。
static class A {int a; }
A對(duì)象占用內(nèi)存情況:
關(guān)閉指針壓縮: 16+4=20不是8的倍數(shù),所以+padding/4=24
開啟指針壓縮: 12+4=16已經(jīng)是8的倍數(shù)了,不需要再padding。
2) 對(duì)reference類型的影響
64位機(jī)器上reference類型占用8個(gè)字節(jié),開啟指針壓縮后占用4個(gè)字節(jié)。
static class B2 {int b2a;Integer b2b;}
B2對(duì)象占用內(nèi)存情況:
關(guān)閉指針壓縮: 16+4+8=28不是8的倍數(shù),所以+padding/4=32
開啟指針壓縮: 12+4+4=20不是8的倍數(shù),所以+padding/4=24
64位機(jī)器上,數(shù)組對(duì)象的對(duì)象頭占用24個(gè)字節(jié),啟用壓縮之后占用16個(gè)字節(jié)。之所以比普通對(duì)象占用內(nèi)存多是因?yàn)樾枰~外的空間存儲(chǔ)數(shù)組的長(zhǎng)度。
先考慮下new Integer[0]占用的內(nèi)存大小,長(zhǎng)度為0,即是對(duì)象頭的大小:
未開啟壓縮:24bytes
開啟壓縮后:16bytes
接著計(jì)算new Integer[1],new Integer[2],new Integer[3]和new Integer[4]就很容易了:
未開啟壓縮:
開啟壓縮:
拿new Integer[3]來(lái)具體解釋下:
未開啟壓縮:24(對(duì)象頭)+8*3=48,不需要padding;
開啟壓縮:16(對(duì)象頭)+3*4=28,+padding/4=32,其他依次類推。
自定義類的數(shù)組也是一樣的,比如:
static class B3 {int a;Integer b; }
new B3[3]占用的內(nèi)存大小:
未開啟壓縮:48
開啟壓縮后:32
復(fù)合對(duì)象計(jì)算復(fù)合對(duì)象占用內(nèi)存的大小其實(shí)就是運(yùn)用上面幾條規(guī)則,只是麻煩點(diǎn)。
1)對(duì)象本身的大小
直接計(jì)算當(dāng)前對(duì)象占用空間大小,包括當(dāng)前類及超類的基本類型實(shí)例字段大小、引用類型實(shí)例字段引用大小、實(shí)例基本類型數(shù)組總占用空間、實(shí)例引用類型數(shù)組引用本身占用空間大小; 但是不包括超類繼承下來(lái)的和當(dāng)前類聲明的實(shí)例引用字段的對(duì)象本身的大小、實(shí)例引用數(shù)組引用的對(duì)象本身的大小。
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(對(duì)象頭)+4(ba)+8(as引用的大小)+padding/4=32
開啟壓縮:12+4+4+padding/4=24
2)當(dāng)前對(duì)象占用的空間總大小
遞歸計(jì)算當(dāng)前對(duì)象占用空間總大小,包括當(dāng)前類和超類的實(shí)例字段大小以及實(shí)例字段引用對(duì)象大小。
遞歸計(jì)算復(fù)合對(duì)象占用的內(nèi)存的時(shí)候需要注意的是:對(duì)齊填充是以每個(gè)對(duì)象為單位進(jìn)行的,看下面這個(gè)圖就很容易明白。
現(xiàn)在我們來(lái)手動(dòng)計(jì)算下C對(duì)象占用的全部?jī)?nèi)存是多少,主要是三部分構(gòu)成:C對(duì)象本身的大小+數(shù)組對(duì)象的大小+B對(duì)象的大小。
未開啟壓縮:
(16 + 4 + 8+4(padding)) + (24+ 8*3) +(16+8)*3 = 152bytes
開啟壓縮:
(12 + 4 + 4 +4(padding)) + (16 + 4*3 +4(數(shù)組對(duì)象padding)) + (12+8+4(B對(duì)象padding))*3= 128bytes
大家有興趣的可以試試。
實(shí)際工作中真正需要手動(dòng)計(jì)算對(duì)象大小的場(chǎng)景應(yīng)該很少,但是個(gè)人覺(jué)得做為基礎(chǔ)知識(shí)每個(gè)Java開發(fā)人員都應(yīng)該了解,另外:對(duì)自己寫的代碼大概占用多少內(nèi)存,內(nèi)存中是怎么布局的應(yīng)該有一個(gè)直覺(jué)性的認(rèn)識(shí)。
相關(guān)文章:
1. 讓chatgpt將html中的圖片轉(zhuǎn)為base64方法示例2. 《CSS3實(shí)戰(zhàn)》筆記--漸變?cè)O(shè)計(jì)(一)3. ASP.NET Core自定義中間件的方式詳解4. 移動(dòng)端HTML5實(shí)現(xiàn)拍照功能的兩種方法5. CSS3實(shí)現(xiàn)動(dòng)態(tài)翻牌效果 仿百度貼吧3D翻牌一次動(dòng)畫特效6. 教你JS更簡(jiǎn)單的獲取表單中數(shù)據(jù)(formdata)7. html5手機(jī)觸屏touch事件介紹8. 用xslt+css讓RSS顯示的跟網(wǎng)頁(yè)一樣漂亮9. python b站視頻下載的五種版本10. 測(cè)試模式 - XSL教程 - 5
