Java對象占用內存大小--Java對象的內存結構分析


個人博客

http://www.milovetingting.cn

Java對象占用內存大小--Java對象的內存結構分析

前言

本文主要介紹Java對象的內存結構

Java對象的內存結構

Java對象的內存結構包括:

  • 對象頭

  • 實例數據

  • 對齊填充

普通對象數組對象,在內存結構上有一些不同,主要體現在對象頭中。普通對象的對象頭由Mark WordKlass Pointer組成,而數組對象,對象頭還包括一個數組長度

具體結構如下圖:

Java對象內存結構.png

對象頭

普通對象:

  • 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對象內存結構-對象頭.png

可以看到,對象頭中的Mark Word占8個字節,Klass Pointer占4個字節,然后補齊了4個字節,總大小為16個字節。

以上結果是VM的默認配置時的輸出。由於測試時的機器為64位HotSpot VM,JDK為1.8,因此是默認開啟了指針壓縮。

64位VM,關閉壓縮

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

-XX:-UseCompressedOops

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

Java對象內存結構-對象頭2.png

和默認開啟指針壓縮不同的是,Klass Pointer占用8個字節,由於Mark Word+Klass Pointer=16,因此不需要再補齊。

由於本機是64位的VM,因此在不壓縮的情況下,Klass Pointer是占用8個字節。而Mark Word不管是否壓縮,都占用8個字節。

Java對象內存結構-對象頭3.png

32位VM

32位VM,不能開啟壓縮。

32位的VM對象頭對應的內存占用大小如下圖:

Java對象內存結構-對象頭4.png

可以借助JOL Java Object Layout的插件進行查看。

在對象類型上右鍵,選擇Show Object Layout

Java對象內存結構-JOL.png

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

Java對象內存結構-JOL2.png

引用類型數組的內存結構

執行以下代碼

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

輸出結果

Java對象內存結構-對象頭5.png

上圖是64位VM,開啟壓縮的內存結構情況。這里只關注數組長度,可以看到長度占4個字節。實際數據占8個字節,即2*4個字節。

關閉壓縮后的結果:

Java對象內存結構-對象頭6.png

可以看到長度占4個字節。實際數據占16個字節,即2*8個字節。

基本類型數組的內存結構

執行以下代碼

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

輸出結果

Java對象內存結構-對象頭7.png

上圖是64位VM,開啟壓縮的內存結構情況。這里只關注數組長度,可以看到長度占4個字節。實際數據占8個字節,即2*4個字節。

關閉壓縮后的結果:

Java對象內存結構-對象頭8.png

可以看到長度占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對象內存結構-對象頭9.png

可以看到,在執行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對象內存結構-對象頭10.png


免責聲明!

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



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