1. 程式人生 > 其它 >如何判斷一個物件佔用多少位元組?

如何判斷一個物件佔用多少位元組?

如何判斷一個物件佔用多少位元組?這是我之前遇到的一個面試題,在這裡分享一下。 要判斷一個物件佔用多少位元組,物件記憶體佈局是必須要了解的。

物件記憶體佈局

在HotSpot虛擬機器裡物件記憶體佈局分為3個部分:物件頭(Header)、例項資料(Instance Data)和對齊填充(Padding)

 

物件頭

物件頭包括兩部分資訊:

  • 第一部分用於儲存物件自身的執行時資料,如雜湊碼(HashCode)、GC 分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒ID、偏向時間戳等。這部分資料的長度在32位和64位的虛擬機器(未開啟壓縮指標) 中分別為32個位元和64個位元, 官方稱它為“Mark Word”。
  • 另外一部分是型別指標,即物件指向它的類元資料的指標,虛擬機器通過這個指標來確定這個物件是哪個類的例項。
  • 如果物件是一個 java 陣列,那麼在物件頭中還有一塊用於記錄陣列長度的資料。

Mark Word的32個位元儲存空間中的25個位元用於儲存物件雜湊碼, 4個位元用於儲存物件分代年齡,2個位元用於儲存鎖標誌位,1個位元固定為0。

HotSpot原始碼 markOop.hpp 註釋片段,描述了Mark Word的記憶體佈局:

 

例項資料

例項資料部分是物件真正儲存的有效資訊,即我們在程式程式碼裡面所定義的各種型別的欄位內容,無論是從父類繼承下來的,還是在子類中定義的欄位都必須記錄起來。HotSpot虛擬機器預設的分配順序為longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers,OOPs),從以上預設的分配策略中可以看到,相同寬度的欄位總是被分配到一起存放,在滿足這個前提條件的情況下,在父類中定義的變數會出現在子類之前。

對齊填充

對齊填充不是必然存在的,也沒有特別的含義,它僅僅起著佔位符的作用。 由於HotSpot虛擬機器的自動記憶體管理系統要求物件起始地址必須是8位元組的整數倍,換句話說就是任何物件的大小都必須是8位元組的整數倍。 物件頭部分已經被精心設計成正好是8位元組的倍數(1倍或者2倍),因此,如果物件例項資料部分沒有對齊的話, 就需要通過對齊填充來補全。

實踐出真知

下面來通過openjdk jol 來解讀物件佔用多少位元組。

JOL

JOL(Java Object Layout)是用於分析 JVM 中物件佈局方案的微型工具箱。這些工具大量使用 Unsafe、JVMTI 和 Serviceability Agent (SA) 來解碼實際的 物件佈局、佔用空間和引用。這使得 JOL 比其他依賴堆轉儲、規範假設等的工具更準確。

示例

  1. 建立一個maven專案,引入依賴
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.16</version>
</dependency>
  1. 新建一個類
@Data
public class BaseEntity  {

    private long id;

    private double amount;

    private int updateUserId;

    private float f;

    private char c;

    private byte b;

    private boolean bb;

    private short ss;

    private long[] list;

    private String s;

    private Long count;
}
  1. 測試
public class ObjectHeaderTest {

    public static void main(String[] args) {
        BaseEntity baseEntity = new BaseEntity();
        String toPrintable = ClassLayout.parseInstance(baseEntity).toPrintable();
        System.out.println(toPrintable);
    }
}

列印結果:

site.sunlong.obj.BaseEntity object internals:
OFF  SZ               TYPE DESCRIPTION               VALUE
  0   8                    (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4                    (object header: class)    0xf800c143
 12   4                int BaseEntity.updateUserId   0
 16   8               long BaseEntity.id             0
 24   8             double BaseEntity.amount         0.0
 32   4              float BaseEntity.f              0.0
 36   2               char BaseEntity.c               
 38   2              short BaseEntity.ss             0
 40   1               byte BaseEntity.b              0
 41   1            boolean BaseEntity.bb             false
 42   2                    (alignment/padding gap)   
 44   4             long[] BaseEntity.list           null
 48   4   java.lang.String BaseEntity.s              null
 52   4     java.lang.Long BaseEntity.count          null
Instance size: 56 bytes
Space losses: 2 bytes internal + 0 bytes external = 2 bytes total
  1. 測試結果解讀 從測試結果可以看到:
  • 資料第1行(object header: mark):這個是Mark Word佔用的位元組數,64位機器上佔用8個位元組,32位機器上佔用4個位元組。
  • 資料第2行(object header: class) :型別指標,在開啟指標壓縮的情況下佔4個位元組,未開啟的情況下佔8個位元組,jdk1.6之後預設開啟指標壓縮。
  • 資料第3-14行(除(alignment/padding gap)):BaseEntity物件屬性型別佔用位元組數,一共佔用39個位元組。
  • 其他資料行:對齊填充2個位元組,由於Mark Word(8個位元組)+型別指標(4個位元組)+物件位元組數(42個位元組)=54個位元組,54不是8的倍數,所以要填充2個位元組湊夠8的倍數。如果位元組數之和剛好是8的倍數,則不需要對齊填充。

從結果也可以得到基本型別及普通物件指標位元組數表格:

型別 位元組數
long/double 8
int/float 4
char/short 2
byte/boolean 1
oops(Ordinary Object Pointers) 4

總結

通過我們上述測試得到的表格,在不依賴openjdk jol的情況下計算出一個物件屬性型別佔多少個位元組之後,開啟指標壓縮時,在物件屬性型別位元組數基礎上加12,未開啟指標壓縮時加16,這樣就能輕鬆的計算出一個物件佔多少個位元組了,從此面試不再怕。

參考資料:

《深入理解Java虛擬機器》