Java對象頭的組成
Java對象的對象頭由 mark word 和 klass pointer 兩部分組成,
mark word存儲了同步狀態、標識、hashcode、GC狀態等等。
klass pointer存儲對象的類型指針,該指針指向它的類元數據
值得注意的是,如果應用的對象過多,使用64位的指針將浪費大量內存。64位的JVM比32位的JVM多耗費50%的內存。
我們現在使用的64位 JVM會默認使用選項 +UseCompressedOops 開啟指針壓縮,將指針壓縮至32位。
以64位操作系統為例,對象頭存儲內容圖例。
|--------------------------------------------------------------------------------------------------------------| | Object Header (128 bits) | |--------------------------------------------------------------------------------------------------------------| | Mark Word (64 bits) | Klass Word (64 bits) | |--------------------------------------------------------------------------------------------------------------| | unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | 無鎖 |----------------------------------------------------------------------|--------|------------------------------| | thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | 偏向鎖 |----------------------------------------------------------------------|--------|------------------------------| | ptr_to_lock_record:62 | lock:2 | OOP to metadata object | 輕量鎖 |----------------------------------------------------------------------|--------|------------------------------| | ptr_to_heavyweight_monitor:62 | lock:2 | OOP to metadata object | 重量鎖 |----------------------------------------------------------------------|--------|------------------------------| | | lock:2 | OOP to metadata object | GC |--------------------------------------------------------------------------------------------------------------|
簡單介紹一下各部分的含義
lock: 鎖狀態標記位,該標記的值不同,整個mark word表示的含義不同。
biased_lock:偏向鎖標記,為1時表示對象啟用偏向鎖,為0時表示對象沒有偏向鎖。

age:Java GC標記位對象年齡。
identity_hashcode:對象標識Hash碼,采用延遲加載技術。當對象使用HashCode()計算后,並會將結果寫到該對象頭中。當對象被鎖定時,該值會移動到線程Monitor中。
thread:持有偏向鎖的線程ID和其他信息。這個線程ID並不是JVM分配的線程ID號,和Java Thread中的ID是兩個概念。
epoch:偏向時間戳。
ptr_to_lock_record:指向棧中鎖記錄的指針。
ptr_to_heavyweight_monitor:指向線程Monitor的指針。
使用JOL工具類,打印對象頭
使用maven的方式,添加jol依賴
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.8</version> </dependency>
創建一個對象A
public class A { boolean flag = false; }
使用jol工具類輸出A對象的對象頭
public static void main(String[] args){ A a = new A(); System.out.println(ClassLayout.parseInstance(a).toPrintable()); }
看看輸出結果
輸出的第一行內容和鎖狀態內容對應
unused:1 | age:4 | biased_lock:1 | lock:2
0 0000 0 01 代表A對象正處於無鎖狀態
第三行中表示的是被指針壓縮為32位的klass pointer
第四行則是我們創建的A對象屬性信息 1字節的boolean值
第五行則代表了對象的對齊字段 為了湊齊64位的對象,對齊字段占用了3個字節,24bit
偏向鎖
public static void main(String[] args) throws InterruptedException { Thread.sleep(5000); A a = new A(); System.out.println(ClassLayout.parseInstance(a).toPrintable()); }
輸出結果
剛開始使用這段代碼我是震驚的,為什么睡眠了5s中就把活生生的A對象由無鎖狀態改變成為偏向鎖了呢?別急,容我慢慢道來!
JVM啟動時會進行一系列的復雜活動,比如裝載配置,系統類初始化等等。在這個過程中會使用大量synchronized關鍵字對對象加鎖,且這些鎖大多數都不是偏向鎖。為了減少初始化時間,JVM默認延時加載偏向鎖。這個延時的時間大概為4s左右,具體時間因機器而異。當然我們也可以設置JVM參數 -XX:BiasedLockingStartupDelay=0 來取消延時加載偏向鎖。
可能你又要問了,我這也沒使用synchronized關鍵字呀,那不也應該是無鎖么?怎么會是偏向鎖呢?
仔細看一下偏向鎖的組成,對照輸出結果紅色划線位置,你會發現占用 thread 和 epoch 的 位置的均為0,說明當前偏向鎖並沒有偏向任何線程。此時這個偏向鎖正處於可偏向狀態,准備好進行偏向了!你也可以理解為此時的偏向鎖是一個
特殊狀態的無鎖。

大家可以看下面這張圖理解一下對象頭的狀態的創建過程

再來看看這段代碼,使用了synchronized關鍵字
public static void main(String[] args) throws InterruptedException { Thread.sleep(5000); A a = new A(); synchronized (a){ System.out.println(ClassLayout.parseInstance(a).toPrintable()); } }
此時對象a,對象頭內容有了明顯的變化,當前偏向鎖偏向主線程。
輕量級鎖
public static void main(String[] args) throws Exception {
Thread.sleep(5000);
A a = new A();
Thread thread1= new Thread(){
@Override
public void run() {
synchronized (a){
System.out.println("thread1 locking");
out.println(ClassLayout.parseInstance(a).toPrintable()); //偏向鎖
}
}
};
thread1.start();
thread1.join();
Thread.sleep(10000);
synchronized (a){
out.println("main locking");
out.println(ClassLayout.parseInstance(a).toPrintable());//輕量鎖
}
}
thread1中依舊輸出偏向鎖,主線程獲取對象A時,thread1雖然已經退出同步代碼塊,但主線程和thread1仍然為鎖的交替競爭關系。故此時主線程輸出結果為輕量級鎖。
重量級鎖
public static void main(String[] args) throws InterruptedException { Thread.sleep(5000); A a = new A(); Thread thread1 = new Thread(){ @Override public void run() { synchronized (a){ System.out.println("thread1 locking"); System.out.println(ClassLayout.parseInstance(a).toPrintable()); try { //讓線程晚點兒死亡,造成鎖的競爭 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread thread2 = new Thread(){ @Override public void run() { synchronized (a){ System.out.println("thread2 locking"); System.out.println(ClassLayout.parseInstance(a).toPrintable()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; thread1.start(); thread2.start(); }
thread1 和 thread2 同時競爭對象a,此時輸出結果為重量級鎖