Java對象內存大小計算


JavaClass基本結構:

名稱 占用字節
Class頭 8字節
oop指針 4字節
數據區域 不定
對其補充 補充到整個大小為8字節的倍數
1. Class頭8個字節, 存儲了比如這個實例目前的鎖信息、目前屬於的堆類型等
2. oop指針,存儲的是這個類的定義,比如Java反射可以拿到字段名稱,方法名稱這些值都是存儲在這個指針所指向的定義中
3. 數據區域,存放數據的區域,這里的結構區分主要是兩種:數組和非數組。如果是數組,數據區域中還會包含這個數組的大小

計算Java對象內存大小有三種方式:

  1. AgentSizeOf : 使用jvm代理和Instrumentation
  2. UnsafeSizeOf : 使用unsafe
  3. ReflectionSizeOf : 通過反射出來Class的成員,通過成員類型進行計算

實例數據:

原生類型(primitive type)的內存占用如下:

Primitive Type Memory Required(bytes)
boolean                       在數組中占1個字節,單獨使用時占4個字節
byte                             1
short                            2
char                             2
int                                4
float                             4
long                             8
double     8

reference類型在32位系統上每個占用4bytes, 在64位系統上每個占用8bytes。

 

關於boolean內存占用 https://www.cnblogs.com/wangtianze/p/6690665.html?utm_source=itdadao&utm_medium=referral

 

對齊填充

HotSpot的對齊方式為8字節對齊:

(對象頭 + 實例數據 + padding) % 8等於0且0 <= padding < 8

 

指針壓縮

對象占用的內存大小收到VM參數UseCompressedOops的影響。32G內存以下的,默認開啟對象指針壓縮。

1)對對象頭的影響

開啟(-XX:+UseCompressedOops)對象頭大小為12bytes(64位機器)。

static class A {
 
    int a;
 
}

A對象占用內存情況:

關閉指針壓縮: 16(對象頭)+4(實例數據)=20不是8的倍數,因此需要對齊填充 16+4+4(padding)=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的倍數,需要對齊填充 16+4+8+4(padding)=32

開啟指針壓縮: 12+4+4=20不是8的倍數,需要對齊填充12+4+4+4(padding)=24

數組對象

64位機器上,數組對象的對象頭占用24個字節(8字節MarkWord+8字節類型指針+8字節數組長度),啟用壓縮之后占用16個字節(8字節MarkWord+4字節類型指針+4字節數組長度)。之所以比普通對象占用內存多是因為需要額外的對象頭空間存儲數組的長度。

先考慮下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(對象頭)+ 4*3 = 28,需要對齊填充 28 + 4(padding) = 32,其他依次類推。

自定義類的數組也是一樣的,比如:

static class B3 {
    int a;
    Integer b;
}

new B3[3]占用的內存大小:

未開啟壓縮:24(對象頭)+ 8*3 = 48

開啟壓縮后:16(對象頭)+ 4*3 + 4(padding) = 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(); 
        }
    }
}        

計算C對象的大小:

未開啟壓縮:16(對象頭)+ 4(ba)+ 8(as引用的大小)+ 4(padding) = 32

開啟壓縮:12(對象頭)+ 4(ba)+4(as引用的大小)+ 4(padding) = 24

2)當前對象占用的空間總大小

遞歸計算當前對象占用空間總大小,包括當前類和超類的實例字段大小以及實例字段引用對象大小。

遞歸計算復合對象占用的內存的時候需要注意的是:對齊填充是以每個對象為單位進行的,看下面這個圖就很容易明白。

 現在我們來手動計算下C對象占用的全部內存是多少,主要是三部分構成:C對象本身的大小+數組對象的大小+B對象的大小。

未開啟壓縮:

(16 + 4 + 8+4(padding)) + (24+ 8*3) +(16+4+4)*3 = 152bytes

開啟壓縮:

(12 + 4 + 4 +4(padding)) + (16 + 4*3 +4(數組對象padding)) + (12+4+4+4(B對象padding)) *3= 128bytes

繼承關系

涉及繼承關系的時候有一個最基本的規則:首先存放父類中的成員,接着才是子類中的成員, 父類也要按照 8 byte 規定

 public static class D {
    byte d1;
}
 
public static class E extends D {
    byte e1;
}

  

計算E對象的大小:

未開啟壓縮:16(對象頭) + 父類(1(d1) + 7(padding)) + 1(e1) + 7(padding) = 32

開啟壓縮:12(對象頭) + 父類(1(d1) + 7(padding)) + 1(e1) + 3(padding) = 24

 

 

參考:https://www.jianshu.com/p/b925b5b5610e?from=timeline&isappinstalled=0

  https://www.cnblogs.com/wangtianze/p/6690665.html?utm_source=itdadao&utm_medium=referral

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM