Synchronized關鍵字------------ 對象頭

Java對象頭的組成

java對象頭分爲兩部分, mark word 和  klass pointer。

mark word 包括: 對象hashcode,,分代年齡4bit,偏向鎖1bit,鎖 類型2bit。

klass pointer存儲對象的類型指針,該指針指向它的類元數據。

64位 JVM會默認使用選項 +UseCompressedOops 開啓指針壓縮,將指針壓縮至32位。

以64位操作系統爲例,對象頭存儲內容圖例。

其中,lock:  鎖狀態標記位,該標記的值不同,整個mark word表示的含義不同。

biased_lock:偏向鎖標記,爲1時表示對象啓用偏向鎖,爲0時表示對象沒有偏向鎖。

age:Java GC標記位對象年齡。爲什麼默認是16歲回收,因爲只有4bit,4bit 1111最大是15。

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());
}

uploading.4e448015.gif轉存失敗重新上傳取消uploading.4e448015.gif轉存失敗重新上傳取消uploading.4e448015.gif轉存失敗重新上傳取消uploading.4e448015.gif轉存失敗重新上傳取消

 uploading.4e448015.gif轉存失敗重新上傳取消uploading.4e448015.gif轉存失敗重新上傳取消uploading.4e448015.gif轉存失敗重新上傳取消uploading.4e448015.gif轉存失敗重新上傳取消輸出結果

剛開始使用這段代碼我是震驚的,爲什麼睡眠了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 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 {
                //thread1退出同步代碼塊,且沒有死亡
                Thread.sleep(3000);
            } 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());
            }
        }
    };
    thread1.start();

       //讓thread1執行完同步代碼塊中方法。
    Thread.sleep(3000);
    thread2.start();
}

thread1中依舊輸出偏向鎖,thread2獲取對象A時,thread1已經退出同步代碼塊,故此時thread2輸出結果爲輕量級鎖。

 

重量級鎖#

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,此時輸出結果爲重量級鎖

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章