前言
當你 new 一個對象時,如果你對它在記憶體中,到底什麼樣,究竟占多大記憶體感興趣。本文可以快速的給您答案。
文章中的例子,預設JVM為64位,無壓縮。
記憶體布局
Java記憶體布局 = 對象頭 + 執行個體資料 +對齊填充
圖檔講解
1.對象頭
對象頭由,markword+類型指針+數組長度組成。
普通對象:markword+類型指針 = 8位元組 + 4位元組 = 12位元組
數組對象:markword+類型指針 + 數組長度 = 8位元組 + 4位元組 +4位元組 = 16位元組
1.1 markword
在64位JVM中,markword是一個8位元組的資料。一位元組8位,是以markword是一個64位的資料。
在32位JVM中,結果不同,這裡不做贅述。
markword圖解
不同的位數,代表含義不同。這裡引用Mark Word 詳解文章中的圖檔。
無鎖對象的markword
可能沒接觸過的同學看不懂該圖,我再舉個例子。我們都知道可以通過synchronized鎖對象,來做同步操作。
一個對象建立出來沒有被鎖過,它的markword應該是下圖這樣的。
其餘鎖的狀态看法,可以參考上圖。
這裡需要注意,hashCode的值,隻有在調用hashcode()方法後,才會改變值。
1.2 類型指針
大小為4位元組,指向方法區中,該對象的類型。
1.3 數組長度
隻有當建立的是數組時,才有該部分,大小為4位元組,代表目前數組的長度。非數組時,不存在。
即:普通對象對象頭 = markword + 類型指針
數組對象 = markword + 類型指針 +數組長度
2.執行個體資料
一個對象的執行個體資料大小,等于它所有成員變量大小,以及繼承類的變量的大小的和。
如果你的對象不繼承任何對象,隻有一個int型變量。那麼你的執行個體資料大小就為4位元組。
如果對象A的父類有一個boolean類型變量,對象A有一個char類型變量。
A對象的執行個體資料大小 = boolean(1位元組)+char(2位元組)
無論父類的變量是private還是public修飾,都會算在子類的執行個體資料中。父類的父類也會算在執行個體資料中。
注意一點,靜态變量存放在方法區,是以不占用對象記憶體。
3.對齊填充
一個對象大小必須為8的整數倍。如果對象頭+執行個體資料 = 57個位元組。那麼對齊填充就為7位元組。對象頭+執行個體資料+對齊填充 = 64位元組。正好8的整數倍。你可以了解為對齊填充就是湊數用的。
執行個體講解
下面通過一個具體例子,以及運作結果,來印證下對象記憶體布局是否如上圖所說。
本文含有運作後的結果。大家可以直接通過代碼和結果來加深印象,無需實機運作代碼。
如何檢視對象
引入一個三方庫:jol,通過這個庫,可以列印對象記憶體布局。
implementation group: 'org.openjdk.jol', name: 'jol-core', version: '0.17'
複制代碼
輔助類Student
/**
* Author(作者):jtl
* Date(日期):2023/3/26 19:20
* Detail(詳情):學生類
*/
public class Student extends Person{
String name = "ZhangSan";// 對象引用:4位元組
boolean isRealMan;//boolean:1位元組
int age = 10;//boolean:1位元組
char score ='A';//char:2位元組
long time = 20230326;//long:8位元組
Student deskMate;//對象引用:4位元組
public Student() {
}
static int grade;
public boolean isRealMan() {
return isRealMan;
}
public void setRealMan(boolean realMan) {
isRealMan = realMan;
}
}
複制代碼
Student的父類Person
該類為了印證,建立Student對象時,Person類的所有變量(包括私有對象),都會占用Student對象的執行個體資料大小。
/**
* Author(作者):jtl
* Date(日期):2023/3/28 16:07
* Detail(詳情):父類,Person類
*/
public class Person extends Object {
private float time = 0L;
String country = "China";
}
複制代碼
實際運作代碼
這裡舉了5種例子,通過下文的運作結果,大家可以進行對比,加深印象。
public class Main {
public static void main(String[] args) throws InterruptedException {
Student student = new Student();
System.out.println("-----------------------------第一階段 new 對象-------------------------------");
System.out.println(ClassLayout.parseInstance(student).toPrintable());
System.out.println("-----------------------------第二階段 hashcode-------------------------------");
student.hashCode();
System.out.println(ClassLayout.parseInstance(student).toPrintable());
synchronized (student){
System.out.println("-----------------------------第三階段 synchronized加鎖-------------------------------");
System.out.println("synchronized (student):\n"+ClassLayout.parseInstance(student).toPrintable());
}
new Thread(new Runnable() {
@Override
public void run() {
synchronized (student){
System.out.println("-----------------------------第四階段 synchronized加鎖-------------------------------");
System.out.println("new Thread:synchronized (student):\n"+ClassLayout.parseInstance(student).toPrintable());
}
}
}).start();
synchronized (student) {
System.out.println("-----------------------------第五階段 Student[] 數組-------------------------------");
System.out.println(ClassLayout.parseInstance(new Student[]{new Student(), new Student(), new Student()}).toPrintable());
}
}
}
複制代碼
記憶體布局
這裡可以看到的是,隻有調用了hashcode方法後。markword中才會修改對應位置的資料。 另外隻有對象為數組時,才會有數組長度(array length) 這個選項,大小為4位元組。
> Task :Main.main()
-----------------------------第一階段 new 對象-------------------------------
org.example.Student object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x01000be8
12 4 float Person.time 0.0
16 4 java.lang.String Person.country (object)
20 4 int Student.age 10
24 8 long Student.time 20230326
32 2 char Student.score A
34 1 boolean Student.isRealMan false
35 1 (alignment/padding gap)
36 4 java.lang.String Student.name (object)
40 4 org.example.Student Student.deskMate null
44 4 (object alignment gap)
Instance size: 48 bytes
Space losses: 1 bytes internal + 4 bytes external = 5 bytes total
-----------------------------第二階段 hashcode-------------------------------
org.example.Student object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00000073035e2701 (hash: 0x73035e27; age: 0)
8 4 (object header: class) 0x01000be8
12 4 float Person.time 0.0
16 4 java.lang.String Person.country (object)
20 4 int Student.age 10
24 8 long Student.time 20230326
32 2 char Student.score A
34 1 boolean Student.isRealMan false
35 1 (alignment/padding gap)
36 4 java.lang.String Student.name (object)
40 4 org.example.Student Student.deskMate null
44 4 (object alignment gap)
Instance size: 48 bytes
Space losses: 1 bytes internal + 4 bytes external = 5 bytes total
-----------------------------第三階段 synchronized加鎖-------------------------------
synchronized (student):
org.example.Student object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000070000dcafac0 (thin lock: 0x000070000dcafac0)
8 4 (object header: class) 0x01000be8
12 4 float Person.time 0.0
16 4 java.lang.String Person.country (object)
20 4 int Student.age 10
24 8 long Student.time 20230326
32 2 char Student.score A
34 1 boolean Student.isRealMan false
35 1 (alignment/padding gap)
36 4 java.lang.String Student.name (object)
40 4 org.example.Student Student.deskMate null
44 4 (object alignment gap)
Instance size: 48 bytes
Space losses: 1 bytes internal + 4 bytes external = 5 bytes total
-----------------------------第五階段 Student[] 數組-------------------------------
[Lorg.example.Student; object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0x0101ed10
12 4 (array length) 3
16 12 org.example.Student Student;.<elements> N/A
28 4 (object alignment gap)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
-----------------------------第四階段 synchronized加鎖-------------------------------
new Thread:synchronized (student):
org.example.Student object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000060000265c002 (fat lock: 0x000060000265c002)
8 4 (object header: class) 0x01000be8
12 4 float Person.time 0.0
16 4 java.lang.String Person.country (object)
20 4 int Student.age 10
24 8 long Student.time 20230326
32 2 char Student.score A
34 1 boolean Student.isRealMan false
35 1 (alignment/padding gap)
36 4 java.lang.String Student.name (object)
40 4 org.example.Student Student.deskMate null
44 4 (object alignment gap)
Instance size: 48 bytes
Space losses: 1 bytes internal + 4 bytes external = 5 bytes total
複制代碼
上面結果的看法如下圖所示:
另外,對齊填充大小如下圖所示。最終對象大小,一定是8的整數倍。
至于 (alignment/padding gap) 什麼情況下會出現。本人沒有仔細研究,感興趣的同學可以看下jol的源碼。下圖為源碼中的具體位置。
結尾
至此,Java對象記憶體布局就講完了。不知道大家還記住多少呢。如果怕看了不久就忘了,可以收藏本文,點個贊支援下作者。文章中若有錯誤,可以及時指正。
今年Java找工作異常艱難,希望大家都可以度過這個寒冬。加油!!!
上文的示例代碼:Java記憶體布局 感興趣的同學,可以下載下傳運作一下。