Java的對象頭原理與源碼超詳細講解
前言
本文將從底層原理和源代碼層面詳細解釋Java的對象頭(Object Header),并且盡量用通俗易懂的語言讓初學者也能理解。首先從概念開始,逐步深入到實現(xiàn)細節(jié),涵蓋對象頭的結(jié)構(gòu)、作用、源碼分析,并提供完整的步驟和推導。內(nèi)容清晰、結(jié)構(gòu)化,避免過于晦澀的技術(shù)術(shù)語。由于對象頭是Java鎖機制(如synchronized)的基礎(chǔ),我會適當結(jié)合鎖的場景來增強理解。
一、什么是Java對象頭?為什么需要它?
1.1 對象頭的概念
在Java中,每個對象(比如new Object()創(chuàng)建的對象)在內(nèi)存中不僅存儲了它的實際數(shù)據(jù)(字段值),還有一個額外的“標簽”部分,稱為對象頭(Object Header)。你可以把對象頭想象成一個身份證,記錄了對象的身份信息和狀態(tài),比如:
- 這個對象是否被鎖???
- 這個對象屬于哪個類?
- 這個對象的年齡(用于垃圾回收)?
對象頭就像一個“管理面板”,JVM(Java虛擬機)通過它來管理對象的生命周期、鎖狀態(tài)和內(nèi)存分配。
1.2 為什么需要對象頭?
對象頭的主要作用是為JVM提供元數(shù)據(jù)(Metadata),支持以下功能:
- 鎖機制:實現(xiàn)synchronized鎖,記錄鎖狀態(tài)(比如哪個線程持有鎖)。
- 垃圾回收:記錄對象的分代年齡(GC Age),決定對象是否需要被回收。
- 類型信息:指向?qū)ο蟮念愋畔?,確保JVM知道這個對象是哪個類的實例。
- 哈希碼:存儲對象的hashCode()值,用于HashMap等數(shù)據(jù)結(jié)構(gòu)。
- 數(shù)組支持:如果對象是數(shù)組,記錄數(shù)組長度。
沒有對象頭,JVM就無法高效管理對象,也無法實現(xiàn)多線程的線程安全。
二、Java對象頭的結(jié)構(gòu)
Java對象頭的結(jié)構(gòu)在JVM實現(xiàn)中(以HotSpot JVM為主)分為幾個部分,主要包括:
- Mark Word:動態(tài)變化的部分,存儲鎖狀態(tài)、哈希碼、分代年齡等。
- Class Metadata Address:指向?qū)ο笏鶎兕惖脑獢?shù)據(jù)地址。
- Array Length(可選):如果對象是數(shù)組,存儲數(shù)組長度。
以下是對象頭在內(nèi)存中的典型布局(以64位JVM為例,假設(shè)未開啟指針壓縮):
| 部分 | 大?。?4位JVM) | 描述 |
|---|---|---|
| Mark Word | 8字節(jié)(64位) | 鎖狀態(tài)、哈希碼、GC年齡等 |
| Class Metadata Address | 8字節(jié)(64位) | 指向類的元數(shù)據(jù)地址 |
| Array Length | 4字節(jié)(可選) | 數(shù)組長度(僅數(shù)組對象有) |
2.1 Mark Word
Mark Word 是對象頭中最復雜、最動態(tài)的部分。它的內(nèi)容會根據(jù)對象狀態(tài)(無鎖、鎖住、GC標記等)變化。Mark Word 通常包含以下信息:
- 鎖狀態(tài):無鎖、偏向鎖、輕量級鎖、重量級鎖。
- 線程ID:持有鎖的線程ID(偏向鎖時)。
- 哈希碼:對象的
hashCode()值。 - 分代年齡:用于垃圾回收,記錄對象經(jīng)歷的GC次數(shù)。
- 鎖記錄指針:輕量級鎖或重量級鎖時,指向鎖記錄或Monitor。
Mark Word 的結(jié)構(gòu)會根據(jù)鎖狀態(tài)動態(tài)調(diào)整。例如,在無鎖狀態(tài)下,它會存儲哈希碼和GC年齡;在鎖住狀態(tài)下,它會存儲線程ID或Monitor指針。
以下是 Mark Word 在不同狀態(tài)下的典型布局(64位JVM,未壓縮指針):
| 鎖狀態(tài) | 63-56bit | 55-2bit | 1-0bit(鎖標志) |
|---|---|---|---|
| 無鎖 | GC年齡 | 對象的哈希碼 | 01 |
| 偏向鎖 | 線程ID | Epoch | 01 |
| 輕量級鎖 | 指向鎖記錄的指針 | 00 | |
| 重量級鎖 | 指向Monitor的指針 | 10 | |
| GC標記 | GC相關(guān)信息 | 11 |
完整位劃分
| 狀態(tài) | 位范圍 | 字段名 | 大?。ㄎ唬?/th> | 說明 |
|---|---|---|---|---|
| 無鎖 | 63-31 | 未使用 | 33 | 保留位,通常為 0,未來可能擴展使用。 |
| 30-8 | 哈希碼 (hash) | 23 | 對象的 hashCode() 值,調(diào)用 System.identityHashCode() 時生成。 | |
| 7-4 | 分代年齡 (age) | 4 | 垃圾回收年齡,記錄對象經(jīng)歷的 Minor GC 次數(shù)(最大 15)。 | |
| 3-2 | 未使用 | 2 | 保留位,通常為 0。 | |
| 1-0 | 鎖標志位 | 2 | 01,表示無鎖狀態(tài)。 | |
| 偏向鎖 | 63-56 | 未使用 | 8 | 保留位,通常為 0。 |
| 55-8 | 線程 ID | 48 | 持有偏向鎖的線程 ID,標識哪個線程“偏向”這個對象。 | |
| 7-6 | Epoch | 2 | 偏向鎖的時間戳,用于批量撤銷偏向鎖(優(yōu)化機制)。 | |
| 5-4 | 分代年齡 (age) | 4 | 同無鎖狀態(tài),記錄 GC 年齡。 | |
| 3 | 偏向鎖標志 | 1 | 1,表示是偏向鎖(與無鎖區(qū)分)。 | |
| 2-1 | 未使用 | 2 | 保留位,通常為 0。 | |
| 0 | 鎖標志位 | 1 | 1,與偏向鎖標志一起組成 01(最低 2 位)。 | |
| 輕量級鎖 | 63-2 | 鎖記錄指針 | 62 | 指向線程棧中的鎖記錄(Displaced Header),保存原來的 Mark Word。 |
| 1-0 | 鎖標志位 | 2 | 00,表示輕量級鎖狀態(tài)。 | |
| 重量級鎖 | 63-2 | Monitor 指針 | 62 | 指向 ObjectMonitor 實例(操作系統(tǒng)級互斥鎖)。 |
| 1-0 | 鎖標志位 | 2 | 10,表示重量級鎖狀態(tài)。 | |
| GC 標記 | 63-2 | GC 相關(guān)信息 | 62 | 存儲垃圾回收標記信息(如對象是否存活,具體由 GC 算法決定)。 |
| 1-0 | 鎖標志位 | 2 | 11,表示 GC 標記狀態(tài)。 |
32位JVM中是這么存的:
鎖狀態(tài) | 25bit | 4bit | 1bit | 2bit | |
23bit | 2bit | 是否偏向鎖 | 鎖標志位 | ||
無鎖 | 對象的哈希碼 | 分代年齡 | 0 | 01 | |
偏向鎖 | 線程ID | Epoch | 分代年齡 | 1 | 01 |
輕量級鎖 | 指向棧中鎖記錄的指針 | 00 | |||
重量級鎖 | 指向重量級鎖的指針 | 10 | |||
GC標記 | 空 | 11 | |||
通俗解釋:
- Mark Word 像一個多功能顯示屏,顯示的內(nèi)容根據(jù)對象狀態(tài)切換。
- 比如,對象沒被鎖時,顯示“哈希碼”和“GC年齡”;被鎖住時,顯示“鎖信息”和“線程ID”。
2.2 Class Metadata Address
這部分是一個指針,指向?qū)ο笏鶎兕惖脑獢?shù)據(jù)(Class Metadata),存儲在JVM的方法區(qū)(或元空間)。元數(shù)據(jù)包含類的結(jié)構(gòu)信息,比如:
- 類名、父類、接口。
- 方法表、字段表。
- 靜態(tài)變量等。
通俗解釋:
- 就像身份證上的“籍貫”,告訴JVM這個對象是哪個類的實例。
- JVM通過這個指針找到類的“藍圖”,知道如何操作這個對象。
2.3 Array Length
如果對象是數(shù)組(比如int[ ]),對象頭會額外包含一個4字節(jié)的字段,記錄數(shù)組的長度。普通對象沒有這一部分。
通俗解釋:
- 數(shù)組就像一個貨架,Array Length 告訴你貨架上有多少格子。
三、對象頭的作用和底層原理
3.1 對象頭在鎖機制中的作用
對象頭是synchronized鎖的核心,因為它存儲了鎖狀態(tài)和Monitor信息。以下是synchronized鎖的工作原理與對象頭的關(guān)聯(lián):
無鎖狀態(tài):
- Mark Word 存儲哈希碼和GC年齡,鎖標志位是
01。 - 對象未被任何線程鎖定。
- Mark Word 存儲哈希碼和GC年齡,鎖標志位是
偏向鎖:
- 當一個線程首次獲取鎖時,JVM將Mark Word中的線程ID設(shè)為該線程ID,鎖標志位仍為
01。 - 偏向鎖假設(shè)鎖通常被同一線程持有,減少鎖獲取的開銷。
- 例如:Mark Word 記錄“線程A的ID”,下次線程A再獲取鎖時,直接檢查ID,無需額外操作。
- 當一個線程首次獲取鎖時,JVM將Mark Word中的線程ID設(shè)為該線程ID,鎖標志位仍為
輕量級鎖:
- 如果有輕微競爭(比如另一個線程嘗試獲取鎖),JVM將鎖升級為輕量級鎖。
- Mark Word 存儲一個指向鎖記錄的指針(如線程棧中的記錄),鎖標志位變?yōu)?code>00。
- 鎖記錄保存了原來的Mark Word內(nèi)容,釋放鎖時恢復。
重量級鎖:
- 如果競爭激烈(多個線程爭搶鎖),JVM將鎖升級為重量級鎖。
- Mark Word 存儲一個指向Monitor的指針,鎖標志位變?yōu)?code>10。
- Monitor 是一個操作系統(tǒng)級別的互斥鎖(Mutex),管理線程的等待和喚醒。
通俗例子:
- 想象對象是一個房間,Mark Word 是門上的鎖。
- 無鎖:門沒鎖,任何人都能進。
- 偏向鎖:門上貼了“只許小明進”的標簽,小明不用每次都開鎖。
- 輕量級鎖:小明和朋友輪流用鑰匙開鎖,稍微麻煩點。
- 重量級鎖:請了個保安(Monitor)守門,所有人排隊登記才能進。
3.2 對象頭在垃圾回收中的作用
Mark Word 中的分代年齡(GC Age)用于JVM的分代垃圾回收(Generational GC):
- 每次對象在Minor GC中存活,年齡加1。
- 當年齡達到閾值(默認15),對象晉升到老年代。
- Mark Word 的GC標記位(
11)在GC期間用于標記對象是否存活。
通俗解釋:
- 分代年齡就像人的年齡,記錄對象“活了多久”。
- JVM通過年齡判斷對象是否“老了”,決定是否搬到“養(yǎng)老院”(老年代)。
3.3 對象頭在哈希碼中的作用
當調(diào)用對象的hashCode()方法時,JVM將哈希碼存儲在Mark Word中。如果對象被鎖住,哈希碼可能被暫時移到Monitor或鎖記錄中。
通俗解釋:
- 哈希碼就像對象的“身份證號”,用于HashMap等場景。
- Mark Word 是身份證的“號碼欄”,鎖住時號碼可能被臨時抄到別處。
四、對象頭的源碼分析
對象頭的實現(xiàn)主要在HotSpot JVM的C++代碼中,位于src/hotspot/share/oops/目錄。我們重點分析Mark Word和相關(guān)邏輯。
4.1 Mark Word的定義
在HotSpot JVM中,Mark Word 由markOop類表示,定義在markOop.hpp中:
// src/hotspot/share/oops/markOop.hpp
class markOopDesc : public oopDesc {
private:
uintptr_t _value; // Mark Word的實際值(64位機器上是64位)
public:
// 獲取鎖狀態(tài)
inline uintptr_t lock_bits() const {
return (_value & lock_mask_in_place);
}
// 獲取線程ID(偏向鎖)
inline uintptr_t biased_thread_id() const {
return (_value >> biased_lock_thread_id_shift);
}
// 獲取哈希碼
inline uintptr_t hash() const {
return (_value >> hash_shift) & hash_mask;
}
// 獲取分代年齡
inline uintptr_t age() const {
return (_value >> age_shift) & age_mask;
}
};
關(guān)鍵點解釋:
_value 是一個64位整數(shù),存儲Mark Word的所有信息。- 通過位運算(
>>、&)提取鎖狀態(tài)、線程ID、哈希碼、年齡等。 - 鎖標志位(最低2位)決定Mark Word的當前狀態(tài)(
01、00、10、11)。
4.2 對象頭的內(nèi)存布局
HotSpot JVM中,對象的內(nèi)存布局由oopDesc類定義,位于oop.hpp:
// src/hotspot/share/oops/oop.hpp
class oopDesc {
private:
volatile markOop _mark; // Mark Word
Klass* _metadata; // Class Metadata Address
};
_mark:Mark Word,存儲鎖狀態(tài)等。_metadata:指向類元數(shù)據(jù)的指針。
如果對象是數(shù)組,還會額外包含數(shù)組長度字段(由JVM在分配內(nèi)存時添加)。
4.3 鎖狀態(tài)的切換邏輯
鎖狀態(tài)的切換在 synchronizer.cpp 中實現(xiàn),涉及偏向鎖、輕量級鎖、重量級鎖的轉(zhuǎn)換。以下是簡化邏輯:
// src/hotspot/share/runtime/synchronizer.cpp
void ObjectSynchronizer::enter(Handle obj, BasicLock* lock, Thread* self) {
markOop mark = obj->mark(); // 獲取Mark Word
if (mark->is_neutral()) { // 無鎖狀態(tài)
// 嘗試偏向鎖
if (UseBiasedLocking) {
markOop biased = mark->biased_to(self); // 設(shè)置線程ID
if (Atomic::cmpxchg(biased, obj->mark_addr(), mark) == mark) {
return; // 偏向成功
}
}
// 嘗試輕量級鎖
lock->set_displaced_header(mark); // 保存Mark Word到鎖記錄
if (Atomic::cmpxchg((markOop)lock, obj->mark_addr(), mark) == mark) {
return; // 輕量級鎖成功
}
}
// 升級到重量級鎖
ObjectMonitor* monitor = inflate_monitor(obj, self);
monitor->enter(self); // 進入Monitor
}
關(guān)鍵點解釋:
- 無鎖到偏向鎖:
- 檢查Mark Word是否為無鎖(is_neutral())。
- 用CAS(
cmpxchg)將線程ID寫入Mark Word。
- 偏向鎖到輕量級鎖:
- 如果有競爭,撤銷偏向鎖(biased_to失敗)。
- 將Mark Word替換為鎖記錄指針,保存原Mark Word到鎖記錄。
- 輕量級鎖到重量級鎖:
- 如果競爭加劇,創(chuàng)建Monitor(inflate_monitor)。
- Mark Word指向Monitor,進入操作系統(tǒng)級鎖。
4.4 Monitor與對象頭的交互
重量級鎖依賴ObjectMonitor類(objectMonitor.hpp),Mark Word存儲Monitor指針:
class ObjectMonitor {
private:
Thread* _owner; // 持有鎖的線程
markOop _header; // 保存原來的Mark Word
// ...
};
- 當鎖升級為重量級鎖,Mark Word指向ObjectMonitor實例。
- 釋放鎖時,ObjectMonitor將保存的 _header(原Mark Word)恢復到對象頭。
五、對象頭的內(nèi)存開銷和優(yōu)化
5.1 內(nèi)存開銷
在64位JVM中,對象頭的典型大?。?/p>
- 普通對象:8字節(jié)(Mark Word)+ 8字節(jié)(Class Metadata Address)= 16字節(jié)。
- 數(shù)組對象:16字節(jié) + 4字節(jié)(Array Length)= 20字節(jié)。
這意味著即使一個空對象(無字段)也有16字節(jié)的開銷,主要來自對象頭。
5.2 指針壓縮
為了減少內(nèi)存開銷,HotSpot JVM支持指針壓縮(-XX:+UseCompressedOops):
- 將64位指針壓縮為32位(通過偏移編碼)。
- 壓縮后,對象頭大小變?yōu)椋?ul>
- 普通對象:8字節(jié)(Mark Word)+ 4字節(jié)(壓縮的Class Metadata Address)= 12字節(jié)。
- 數(shù)組對象:12字節(jié) + 4字節(jié)(Array Length)= 16字節(jié)。
通俗解釋:
- 指針壓縮就像把“詳細地址”的省市縣簡化成“郵編”,節(jié)省空間。
5.3 鎖優(yōu)化
對象頭的鎖狀態(tài)切換(偏向鎖 → 輕量級鎖 → 重量級鎖)是JVM的性能優(yōu)化:
- 偏向鎖:適合單線程場景,Mark Word直接記錄線程ID,獲取鎖幾乎無開銷。
- 輕量級鎖:適合低競爭場景,用CAS操作鎖記錄,減少系統(tǒng)調(diào)用。
- 重量級鎖:適合高競爭場景,依賴操作系統(tǒng)Mutex,但開銷大。
六、完整流程
對象頭的定義:
- 每個Java對象在內(nèi)存中包含對象頭,分為Mark Word、Class Metadata Address、Array Length(可選)。
- Mark Word 是動態(tài)部分,存儲鎖狀態(tài)、哈希碼、GC年齡等。
對象頭的作用:
- 鎖機制:通過Mark Word實現(xiàn)synchronized的偏向鎖、輕量級鎖、重量級鎖。
- 垃圾回收:記錄分代年齡,支持分代GC。
- 類型信息:指向類元數(shù)據(jù),確定對象類型。
- 哈希碼:存儲hashCode()值。
底層實現(xiàn):
- Mark Word 由markOop類管理,通過位運算提取信息。
- 鎖狀態(tài)切換由synchronizer.cpp實現(xiàn),涉及CAS和Monitor。
- 對象頭與JVM的內(nèi)存管理和線程調(diào)度緊密協(xié)作。
內(nèi)存優(yōu)化:
- 指針壓縮減少對象頭大小(16字節(jié) → 12字節(jié))。
- 鎖優(yōu)化(偏向鎖、輕量級鎖)降低鎖開銷。
七、通俗總結(jié)
- 對象頭是什么?它是對象的“身份證”,記錄鎖狀態(tài)、類型信息、年齡等。
- 怎么工作?Mark Word 像一個多功能顯示屏,根據(jù)對象狀態(tài)切換顯示內(nèi)容(鎖、哈希碼等)。
- 為什么重要?沒有對象頭,JVM無法實現(xiàn)鎖、垃圾回收等核心功能。
- 底層實現(xiàn)?通過HotSpot JVM的C++代碼(markOop、synchronizer)管理,依賴位運算和操作系統(tǒng)支持。
生活化比喻:
- 對象頭就像一個智能門牌,平時顯示房間號(類型信息)、住戶ID(鎖狀態(tài))、房屋年齡(GC年齡)。
- 當有人敲門(線程訪問),門牌會切換顯示“誰能進”(鎖信息),還能記錄“敲門次數(shù)”(哈希碼)。
八、擴展閱讀
- 源碼推薦:
- HotSpot JVM:markOop.hpp(Mark Word定義)、synchronizer.cpp(鎖邏輯)。
- 相關(guān)類:oop.hpp(對象布局)、objectMonitor.hpp(Monitor實現(xiàn))。
- 工具:
- 用jmap -histo查看對象內(nèi)存占用,分析對象頭開銷。
- 用jol(Java Object Layout)庫查看對象頭結(jié)構(gòu)。
- 書籍:
- 《深入理解Java虛擬機》(周志明):深入講解JVM內(nèi)存和對象頭。
- 《Java并發(fā)編程實戰(zhàn)》:結(jié)合鎖機制理解對象頭。
到此這篇關(guān)于Java的對象頭原理與源碼超詳細講解的文章就介紹到這了,更多相關(guān)Java對象頭原理詳解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java語言實現(xiàn)簡單FTP軟件 FTP協(xié)議分析(1)
這篇文章主要介紹了Java語言實現(xiàn)簡單FTP軟件的第一篇,針對FTP協(xié)議進行分析,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03
Java數(shù)據(jù)結(jié)構(gòu)專題解析之棧和隊列的實現(xiàn)
從數(shù)據(jù)結(jié)構(gòu)的定義看,棧和隊列也是一種線性表。其不同之處在于棧和隊列的相關(guān)運算具有特殊性,只是線性表相關(guān)運算的一個子集。更準確的說,一般線性表的插入、刪除運算不受限制,而棧和隊列上的插入刪除運算均受某種特殊限制。因此,棧和隊列也稱作操作受限的線性表2021-10-10
Java中數(shù)組容器(ArrayList)設(shè)的計與實現(xiàn)
本篇文章主要跟大家介紹我們最常使用的一種容器ArrayList、Vector的原理,并且自己使用Java實現(xiàn)自己的數(shù)組容器MyArrayList,讓自己寫的容器能像ArrayList那樣工作,感興趣的可以了解一下2022-07-07
SpringBoot?+?Redis如何解決重復提交問題(冪等)
在開發(fā)中,一個對外暴露的接口可能會面臨瞬間的大量重復請求,本文就介紹了SpringBoot + Redis如何解決重復提交問題,具有一定的參考價值,感興趣的可以了解一下2021-12-12

