個人博客
Java對象占用內存大小--Java對象的內存結構分析
前言
本文主要介紹Java對象的內存結構
。
Java對象的內存結構
Java對象的內存結構包括:
-
對象頭
-
實例數據
-
對齊填充
普通對象
和數組對象
,在內存結構上有一些不同,主要體現在對象頭
中。普通對象的對象頭由Mark Word
和Klass Pointer
組成,而數組對象,對象頭還包括一個數組長度
。
具體結構如下圖:
對象頭
普通對象:
-
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());
輸出結果:
可以看到,對象頭中的Mark Word
占8個字節,Klass Pointer
占4個字節,然后補齊了4個字節,總大小為16個字節。
以上結果是VM的默認配置
時的輸出。由於測試時的機器為64位HotSpot VM,JDK為1.8,因此是默認開啟了指針壓縮。
64位VM,關閉壓縮
下面通過修改VM參數,來關閉指針壓縮:
-XX:-UseCompressedOops
再次執行測試代碼,輸出結果:
和默認開啟指針壓縮不同的是,Klass Pointer
占用8個字節,由於Mark Word+Klass Pointer=16,因此不需要再補齊。
由於本機是64位的VM,因此在不壓縮的情況下,Klass Pointer是占用8個字節。而Mark Word不管是否壓縮,都占用8個字節。
32位VM
32位VM,不能開啟壓縮。
32位的VM對象頭對應的內存占用大小如下圖:
可以借助JOL Java Object Layout
的插件進行查看。
在對象類型上右鍵
,選擇Show Object Layout
在彈出的界面中選擇32位VM,可以看到Object是占用8個字節,即4個字節的Mark Word+4個字節的Klass Pointer。
引用類型數組的內存結構
執行以下代碼
Object[] objects = {new Object(), new Object()};
System.out.println(ClassLayout.parseInstance(objects).toPrintable());
輸出結果
上圖是64位VM,開啟壓縮的內存結構情況。這里只關注數組長度,可以看到長度占4個字節。實際數據占8個字節,即2*4個字節。
關閉壓縮后的結果:
可以看到長度占4個字節。實際數據占16個字節,即2*8個字節。
基本類型數組的內存結構
執行以下代碼
int[] nums = {1,2};
System.out.println(ClassLayout.parseInstance(nums).toPrintable());
輸出結果
上圖是64位VM,開啟壓縮的內存結構情況。這里只關注數組長度,可以看到長度占4個字節。實際數據占8個字節,即2*4個字節。
關閉壓縮后的結果:
可以看到長度占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());
}
執行結果
可以看到,在執行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);
執行結果