天天看點

Java對象占用記憶體大小--Java對象的記憶體結構分析Java對象占用記憶體大小–Java對象的記憶體結構分析

個人部落格

http://www.milovetingting.cn

Java對象占用記憶體大小–Java對象的記憶體結構分析

前言

本文主要介紹Java對象的

記憶體結構

Java對象的記憶體結構

Java對象的記憶體結構包括:

  • 對象頭

  • 執行個體資料

  • 對齊填充

普通對象

數組對象

,在記憶體結構上有一些不同,主要展現在

對象頭

中。普通對象的對象頭由

Mark Word

Klass Pointer

組成,而數組對象,對象頭還包括一個

數組長度

具體結構如下圖:

Java對象占用記憶體大小--Java對象的記憶體結構分析Java對象占用記憶體大小–Java對象的記憶體結構分析

對象頭

普通對象:

  • Mark Word

    :包含HashCode、分代年齡、鎖标志等。
  • Klass Pointer

    :指向目前對象的Class對象的記憶體位址。

數組對象:

  • Mark Word

    :包含HashCode、分代年齡、鎖标志等。
  • Klass Pointer

    :指向目前對象的Class對象的記憶體位址。
  • Length

    :數組長度

執行個體資料

存儲對象的所有成員變量,

static

成員變量不包括在内。

對齊填充

Java對象的記憶體空間是

8位元組對齊

的,是以總大小不是8的倍數時,會進行補齊。

Java對象的記憶體占用大小分析

工具:

JOL

為便于分析對象的記憶體結構,可以使用

JOL(Java Object Layout)

工具來檢視,位址:https://openjdk.java.net/projects/code-tools/jol/

插件:JOL Java Object Layout

也可以使用IDEA插件,進行可視化分析

https://plugins.jetbrains.com/plugin/10953-jol-java-object-layout

具體分析

64位VM,開啟壓縮

首先,看下Object的記憶體結構。

引入JOL的jar包,通過下面代碼就可以看到記憶體結構:

Object object = new Object();
System.out.println(ClassLayout.parseInstance(object).toPrintable());
           

輸出結果:

Java對象占用記憶體大小--Java對象的記憶體結構分析Java對象占用記憶體大小–Java對象的記憶體結構分析

可以看到,對象頭中的

Mark Word

占8個位元組,

Klass Pointer

占4個位元組,然後補齊了4個位元組,總大小為16個位元組。

以上結果是VM的

預設配置

時的輸出。由于測試時的機器為64位HotSpot VM,JDK為1.8,是以是預設開啟了指針壓縮。

64位VM,關閉壓縮

下面通過修改VM參數,來關閉指針壓縮:

-XX:-UseCompressedOops
           

再次執行測試代碼,輸出結果:

Java對象占用記憶體大小--Java對象的記憶體結構分析Java對象占用記憶體大小–Java對象的記憶體結構分析

和預設開啟指針壓縮不同的是,

Klass Pointer

占用8個位元組,由于Mark Word+Klass Pointer=16,是以不需要再補齊。

由于本機是64位的VM,是以在不壓縮的情況下,Klass Pointer是占用8個位元組。而Mark Word不管是否壓縮,都占用8個位元組。

Java對象占用記憶體大小--Java對象的記憶體結構分析Java對象占用記憶體大小–Java對象的記憶體結構分析

32位VM

32位VM,不能開啟壓縮。

32位的VM對象頭對應的記憶體占用大小如下圖:

Java對象占用記憶體大小--Java對象的記憶體結構分析Java對象占用記憶體大小–Java對象的記憶體結構分析

可以借助

JOL Java Object Layout

的插件進行檢視。

在對象類型上

右鍵

,選擇

Show Object Layout

Java對象占用記憶體大小--Java對象的記憶體結構分析Java對象占用記憶體大小–Java對象的記憶體結構分析

在彈出的界面中選擇32位VM,可以看到Object是占用8個位元組,即4個位元組的Mark Word+4個位元組的Klass Pointer。

Java對象占用記憶體大小--Java對象的記憶體結構分析Java對象占用記憶體大小–Java對象的記憶體結構分析

引用類型數組的記憶體結構

執行以下代碼

Object[] objects = {new Object(), new Object()};
System.out.println(ClassLayout.parseInstance(objects).toPrintable());
           

輸出結果

Java對象占用記憶體大小--Java對象的記憶體結構分析Java對象占用記憶體大小–Java對象的記憶體結構分析

上圖是64位VM,開啟壓縮的記憶體結構情況。這裡隻關注數組長度,可以看到長度占4個位元組。實際資料占8個位元組,即2*4個位元組。

關閉壓縮後的結果:

Java對象占用記憶體大小--Java對象的記憶體結構分析Java對象占用記憶體大小–Java對象的記憶體結構分析

可以看到長度占4個位元組。實際資料占16個位元組,即2*8個位元組。

基本類型數組的記憶體結構

執行以下代碼

int[] nums = {1,2};
System.out.println(ClassLayout.parseInstance(nums).toPrintable());
           

輸出結果

Java對象占用記憶體大小--Java對象的記憶體結構分析Java對象占用記憶體大小–Java對象的記憶體結構分析

上圖是64位VM,開啟壓縮的記憶體結構情況。這裡隻關注數組長度,可以看到長度占4個位元組。實際資料占8個位元組,即2*4個位元組。

關閉壓縮後的結果:

Java對象占用記憶體大小--Java對象的記憶體結構分析Java對象占用記憶體大小–Java對象的記憶體結構分析

可以看到長度占4個位元組,由于:(8個位元組的Mark Word+8個位元組的Klass Pointer+4個位元組的Length+8個位元組的資料長度)不是8的倍數,是以進行了4個位元組的補齊。實際資料占8個位元組,即2*4個位元組。

小結

  • 32位的VM

    Mark Word占用4個位元組,Klass Pointer占用4個位元組,數組長度占用4個位元組。實際資料:引用類型占用4個位元組。

  • 64位的VM
    • 開啟壓縮

      Mark Word占用8個位元組,Klass Pointer占用4個位元組,數組長度占用4個位元組。實際資料:引用類型占用4個位元組。

    • 關閉壓縮

      Mark Word占用8個位元組,Klass Pointer占用8個位元組,數組長度占用4個位元組。實際資料:引用類型占用8個位元組。

對象頭中鎖辨別

執行以下代碼,分析加鎖前後對象頭的資料變化

Object object = new Object();
System.out.println(ClassLayout.parseInstance(object).toPrintable());
synchronized (object) {
    System.out.println(ClassLayout.parseInstance(object).toPrintable());
}
           

執行結果

Java對象占用記憶體大小--Java對象的記憶體結構分析Java對象占用記憶體大小–Java對象的記憶體結構分析

可以看到,在執行synchronized代碼裡,Object的對象頭資料發生了變化,這是因為鎖辨別是存放在對象頭中的,在執行synchronized代碼時,會對鎖進行辨別。

JOL常用方法

JOL常用的三個方法

  • ClassLayout.parseInstance(object).toPrintable():檢視對象内部資訊
  • GraphLayout.parseInstance(object).toPrintable():檢視對象外部資訊,包括引用的對象
  • GraphLayout.parseInstance(object).totalSize():檢視對象總大小
List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 15; i++) {
            list.add(i);
        }
        //檢視對象内部資訊
        String innerInfo = ClassLayout.parseInstance(list).toPrintable();
        System.out.println("對象内部資訊");
        System.out.println(innerInfo);
        //檢視對象外部資訊,包括引用的對象
        String outInfo = GraphLayout.parseInstance(list).toPrintable();
        System.out.println("對象外部資訊");
        System.out.println(outInfo);
        //檢視對象總大小
        long totalSize = GraphLayout.parseInstance(list).totalSize();
        System.out.println("對象總大小");
        System.out.println(totalSize);
           

執行結果

Java對象占用記憶體大小--Java對象的記憶體結構分析Java對象占用記憶體大小–Java對象的記憶體結構分析