一個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菜鳥。
在介紹之前,簡單回顧下,Java物件的記憶體佈局:物件頭(Header),例項資料(Instance Data)和對齊填充(Padding),詳細的可以看我的讀書筆記。另外:不同的環境結果可能有差異,我所在的環境是HotSpot虛擬機器,64位Windwos。
下面進入正文:
物件頭
物件頭在32位系統上佔用8bytes,64位系統上佔用16bytes。
例項資料
原生型別(primitive type)的記憶體佔用如下:
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位元組對齊:
(物件頭 + 例項資料 + padding) % 8等於0且0 <= padding < 8
指標壓縮
物件佔用的記憶體大小收到VM引數UseCompressedOops的影響。
1)對物件頭的影響
開啟(-XX:+UseCompressedOops)物件頭大小為12bytes(64位機器)。
static class A { int a; }
A物件佔用記憶體情況:
關閉指標壓縮: 16+4=20不是8的倍數,所以+padding/4=24
開啟指標壓縮: 12+4=16已經是8的倍數了,不需要再padding。
2) 對reference型別的影響
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
複合物件
計算複合物件佔用記憶體的大小其實就是運用上面幾條規則,只是麻煩點。
1)物件本身的大小
直接計算當前物件佔用空間大小,包括當前類及超類的基本型別例項欄位大小、引用型別例項欄位引用大小、例項基本型別陣列總佔用空間、例項引用型別陣列引用本身佔用空間大小; 但是不包括超類繼承下來的和當前類宣告的例項引用欄位的物件本身的大小、例項引用陣列引用的物件本身的大小。
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
2)當前物件佔用的空間總大小
遞迴計算當前物件佔用空間總大小,包括當前類和超類的例項欄位大小以及例項欄位引用物件大小。
遞迴計算複合物件佔用的記憶體的時候需要注意的是:對齊填充是以每個物件為單位進行的,看下面這個圖就很容易明白。
現在我們來手動計算下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開發人員都應該瞭解,另外:對自己寫的程式碼大概佔用多少記憶體,記憶體中是怎麼佈局的應該有一個直覺性的認識。
相關文章
- 一個Java物件到底佔用多大記憶體?Java物件記憶體
- 高階面試必備:一個Java物件佔用多大記憶體面試Java物件記憶體
- 電腦記憶體多大最好?談談電腦到底需要多大記憶體記憶體
- 如何檢視MySQL資料庫佔多大記憶體,佔用太多記憶體怎麼辦?MySql資料庫記憶體
- Java中的String到底佔用多大的記憶體空間?你所瞭解的可能都是錯誤的!!Java記憶體
- Java物件記憶體模型Java物件記憶體模型
- Java 物件記憶體分析Java物件記憶體
- python物件的記憶體佔用Python物件記憶體
- Java物件記憶體佈局Java物件記憶體
- PHP陣列到底佔用多少記憶體空間PHP陣列記憶體
- JVM記憶體結構、Java記憶體模型和Java物件模型JVM記憶體Java模型物件
- win10系統檔案有多大 win10映象檔案佔用多大記憶體Win10記憶體
- iOS底層原理(一):OC物件實際佔用記憶體與開闢記憶體關係iOS物件記憶體
- Java物件的記憶體佈局Java物件記憶體
- filebeat實踐-記憶體佔用-最大記憶體佔用記憶體
- Object o = new Object()佔多少個位元組?-物件的記憶體佈局Object物件記憶體
- win10執行記憶體多大才夠用?windows10需要多大執行記憶體Win10記憶體Windows
- 淺談JVM記憶體結構 和 Java記憶體模型 和 Java物件模型JVM記憶體Java模型物件
- JAVA物件在JVM中記憶體分配Java物件JVM記憶體
- java 產生一個Java的記憶體洩露Java記憶體洩露
- 【HotSpot】一個java物件佔多少空間HotSpotJava物件
- Java記憶體分析一Java記憶體
- 智慧手機到底多大記憶體才算夠用?專家說4GB足矣記憶體
- 物件記憶體圖物件記憶體
- NSObject 物件佔用記憶體、isa/superclass指向、類資訊存放Object物件記憶體
- 32位win10系統支援多大記憶體_32位win10系統能識別多大記憶體Win10記憶體
- Java記憶體模型FAQ(一) 什麼是記憶體模型Java記憶體模型
- Android記憶體優化(一):Java記憶體區域Android記憶體優化Java
- 修改oracle記憶體佔用Oracle記憶體
- 資源記憶體佔用記憶體
- 圖文詳解Java物件記憶體佈局Java物件記憶體
- Java物件記憶體分配原理及原始碼分析Java物件記憶體原始碼
- Java逐層解析JSON的記憶體佔用分析JavaJSON記憶體
- Java的記憶體 -JVM 記憶體管理Java記憶體JVM
- Java常見知識點彙總(⑱)——Jvm記憶體結構、Java記憶體模型、Java物件模型的區別JavaJVM記憶體模型物件
- Java虛擬機器2:Java記憶體區域及物件Java虛擬機記憶體物件
- Java中物件並不是都在堆上分配記憶體的。Java物件記憶體
- node計算記憶體佔用記憶體