個人部落格
http://www.milovetingting.cn
Java對象占用記憶體大小–Java對象的記憶體結構分析
前言
本文主要介紹Java對象的
記憶體結構
。
Java對象的記憶體結構
Java對象的記憶體結構包括:
-
對象頭
-
執行個體資料
-
對齊填充
普通對象
和
數組對象
,在記憶體結構上有一些不同,主要展現在
對象頭
中。普通對象的對象頭由
Mark Word
和
Klass Pointer
組成,而數組對象,對象頭還包括一個
數組長度
。
具體結構如下圖:
對象頭
普通對象:
-
:包含HashCode、分代年齡、鎖标志等。Mark Word
-
:指向目前對象的Class對象的記憶體位址。Klass Pointer
數組對象:
-
:包含HashCode、分代年齡、鎖标志等。Mark Word
-
:指向目前對象的Class對象的記憶體位址。Klass Pointer
-
:數組長度Length
執行個體資料
存儲對象的所有成員變量,
static
成員變量不包括在内。
對齊填充
Java對象的記憶體空間是
8位元組對齊
的,是以總大小不是8的倍數時,會進行補齊。
Java對象的記憶體占用大小分析
工具: JOL
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);
執行結果