驗證synchronized鎖升級時對象頭變化全過程 - springboot實戰電商項目mall4j

springboot實戰電商項目mall4j (https://gitee.com/gz-yami/mall4j)java

java開源商城系統git

驗證synchronized鎖升級時對象頭變化全過程

jdk版本:1.8spring

系統:window10 64位數組

jvm 啓動參數:-XX:BiasedLockingStartupDelay=0 (取消延遲加載偏向鎖)springboot


首先須要已知幾個概念jvm

  1. java 非數組對象(普通對象)的內存結構

1620543083409.png

​ 若是是 array 對象,則會再佔用一個 length 空間(4 字節),記錄數組的長度。工具

  1. java object 的 markword 格式(64位虛擬機上)

1620544035295.png

3.synchronized 四鎖升級流程以及什麼時候升級(不贅述)編碼


經過實際編碼查看分別在這四個鎖狀態時,鎖標誌位是否相應變化spa

引入 jol 工具包線程

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

無鎖態

@Test
public void test01() throws Exception {
    Object o = new Object();
    System.out.println(ClassLayout.parseInstance(o).toPrintable());
}

執行結果

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

第1、二行的 object header 是 markword 的內存,第三行的 object header 是 class pointer 的內容,第四行是對齊填充。

只需關注第1、二行。

第1、二行由於是由低位打印到高位的,因此須要反過來看纔會和上方的鎖狀態表格中一一對應。

即 <u>00000000 00000000 00000000 00000000 00000000 00000000 000000</u>00 00000101

對照表格,重點最後三位,是否偏向鎖爲1,鎖標誌位爲01,理論上來講偏向鎖標記應該爲0,這是由於咱們加了個 取消延遲加載偏向鎖的啓動參數致使的,若是把啓動參數去掉,那麼偏向鎖標誌位就是0。

JVM啓動時會進行一系列的複雜活動,好比裝載配置,系統類初始化等等。在這個過程當中會使用大量synchronized關鍵字對對象加鎖,且這些鎖大多數都不是偏向鎖。爲了減小初始化時間,JVM默認延時加載偏向鎖,而咱們把它禁用掉了,因此偏向鎖標誌位就變成了 1。

即使如此,依舊能夠看出下劃線部分爲偏向線程的 id 存儲位置,目前全爲 0,也即這個對象目前不偏向任何線程,因此當前對象仍是可偏向的狀態

即當前對象鎖狀態爲無鎖態得證。

偏向鎖態

@Test
public void test2() throws Exception {
    Object o = new Object();
    synchronized (o) {}
    System.out.println(ClassLayout.parseInstance(o).toPrintable());
}

執行結果

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 e8 d1 02 (00000101 11101000 11010001 00000010) (47310853)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

同樣先把前兩行按表格中的順序先排列好,以下:

<u>00000000 00000000 00000000 00000000 00000010 11010001 111010</u>00 00000101

這回能夠看出偏向鎖標誌位1,鎖標誌位爲01,並且偏向鎖的線程 id 也被佔用了,因此顯然該對象線程是偏向鎖態。

同時,從代碼中看出,是先對 o 上 synchronized 鎖且鎖釋放以後纔對 o 進行打印的,能夠得出一個結論,偏向鎖狀態不會主動撤銷,而是會繼續保留其狀態。

輕量級鎖

@Test
public void test3() throws Exception {
    Object o = new Object();
    synchronized (o) {}
    new Thread(() -> {
        synchronized (o) {
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }).start();
    TimeUnit.SECONDS.sleep(10);
}

執行結果

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           00 ef a5 1e (00000000 11101111 10100101 00011110) (514191104)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

00000000 00000000 00000000 00000000 00011110 10100101 11101111 00000000

對照表,顯然此時的 o 處於輕量級鎖態。由於主線程先對 o 上鎖,o處於偏向鎖,而後再來個線程對 o 上鎖,上鎖前就偏向鎖就會膨脹爲輕量級鎖。

綜上,得證。

重量級鎖

@Test
public void test4() throws Exception {
    Object o = new Object();
    AtomicInteger index = new AtomicInteger(0);
    for (int i = 0; i < 10; i++) {
        new Thread(() -> {
            synchronized (o) {
                index.getAndIncrement();
                if(index.get() == 9)
                System.out.println(ClassLayout.parseInstance(o).toPrintable());
            }
        }).start();
    }
    TimeUnit.SECONDS.sleep(10);
}

執行結果

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           fa 05 d7 1c (11111010 00000101 11010111 00011100) (483853818)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

00000000 00000000 00000000 00000000 00011100 11010111 00000101 11111010

出現大量鎖競爭,理論上 o 應當處於重量級鎖態,對照表格顯然得出是 o 處於重量級鎖態,得證。


再來看以下兩種狀況

  1. 在對 o 上鎖前計算 o 的 hashcode,上鎖時 o 處於什麼狀態

    @Test
    public void test5() throws Exception {
        Object o = new Object();
        o.hashCode();
        synchronized (o) {
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }

從理論上將應該仍是偏向鎖,那麼看執行結果。

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           b8 e4 ac 02 (10111000 11100100 10101100 00000010) (44885176)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

此時的鎖標誌位爲 00,爲輕量級鎖,並非偏向鎖。

結論:計算過 hashcode 的對象,在上鎖後會直接膨脹爲輕量級鎖,跳過偏向鎖。

  1. 在對 o 上鎖後計算 o 的hashcode,此時的 o 處於什麼狀態

    @Test
    public void test6() throws Exception {
        Object o = new Object();
        synchronized (o) {
            o.hashCode();
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }

理論上這裏的 o 應該是偏向鎖。看執行結果:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           ba c7 52 1c (10111010 11000111 01010010 00011100) (475187130)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

此時的鎖標誌位爲 10,爲重量級鎖,並非偏向鎖。

結論:處於偏向鎖態的對象只要計算過 hashcode,會直接膨脹爲重量級鎖。

拓展:

檢驗代碼中計算出來的 hashcode 與 markword 中對應記錄 hashcode 中的值一致

@Test
public void test7() throws Exception {
    Object o = new Object();
    System.out.println(o.hashCode());
    System.out.println(ClassLayout.parseInstance(o).toPrintable());
}

輸出結果:

1174290147
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 e3 3e fe (00000001 11100011 00111110 11111110) (-29433087)
      4     4        (object header)                           45 00 00 00 (01000101 00000000 00000000 00000000) (69)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

將 markword 抄寫出來,即 00000000 00000000 00000000 01000101 11111110 00111110 11100011 00000001

對照文章開頭的表格,可知從第26位開始到底56位都是記錄 hashcode 的比特位。即 1000101 11111110 00111110 11100011,將其轉爲十進制,結果爲 1,174,290,147。和代碼中輸出的結果一致。

springboot實戰電商項目mall4j (https://gitee.com/gz-yami/mall4j)

java開源商城系統

相關文章
相關標籤/搜索