java併發筆記之synchronized 偏向鎖 輕量級鎖 重量級鎖證明

王六六666發表於2019-07-30

 警告⚠️:本文耗時很長,先做好心理準備

本篇將從hotspot原始碼(64 bits)入手,通過分析java物件頭引申出鎖的狀態;本文采用大量例項及分析,請耐心看完,謝謝
 
先來看一下hotspot的原始碼當中的物件頭的註釋(32bits 可以忽略了,現在基本沒有32位作業系統):
*  Bit-format of an object header (most significant first, big endian layout below):
*  32 bits:
*  --------
*             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
*             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
*             size:32 ------------------------------------------>| (CMS free block)
*             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
*
*  64 bits:
*  --------
*  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
*  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
*  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
*  size:64 ----------------------------------------------------->| (CMS free block)
*
*  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
*  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
*  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
*  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
以64 bits為主翻譯:
|======================================================================|========================|=======================|
|                                                    Object  Header (128bits)                                           |
|======================================================================|========================|=======================|
|                             Mark Word(64bits)                        | klass Word(64bits)     |                       
|                                                                      | 暫不考慮開啟指標壓縮的場景 |       鎖的狀態        
|======================================================================|========================|=======================|
| unused:25 | hash:31 | unused:1      | age:4 | biased_lock:1 |lock:2  | OOP to metadata object |        無鎖    0 01
|-----------------------------------------------------------------------------------------------|-----------------------|
  註解: unused:25 + hash:31 = 56 bits--> hashcode ; unused:未使用   ; age :GC分代年齡|偏向鎖標識 ; lock: 物件的狀態                                                                                                                    
|===============================================================================================|=======================|
| JavaThread*:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object |        偏向鎖  1 01      
|-----------------------------------------------------------------------------------------------|-----------------------|
  註解:JavaThread:執行緒;epoch:記住撤銷偏向鎖次數(偏向時間戳);unused:未使用;age :GC分代年齡|偏向鎖標識; lock: 物件的狀態    
|===============================================================================================|=======================|
|                ptr_to_lock_record:62                        | lock:2 | OOP to metadata object |       輕量級鎖   00      
|-----------------------------------------------------------------------------------------------|-----------------------|
  註解:    ptr_to_lock_record:指向棧中鎖記錄的指標 ;             lock: 物件的狀態                                         
|===============================================================================================|=======================|
|             ptr_to_heavyweight_monitor:62                  | lock:2 | OOP to metadata object |        重量級鎖   10            
|-----------------------------------------------------------------------------------------------|-----------------------|
  註解:   ptr_to_heavyweight_monitor:指向管程Monitor的指標 ;       lock: 物件的狀態                                        
|===============================================================================================|=======================|
|                                                             | lock:2 | OOP to metadata object |        GC標記    01        
|-----------------------------------------------------------------------------------------------|-----------------------|
  註解:                              空,不需要記錄資訊 ;        lock: 物件的狀態                                    
|===============================================================================================|=======================|
由上可以知道java的物件頭在物件的不同狀態下會有不同的表現形式,主要有三種狀態,無鎖狀態、加鎖狀態、gc標記狀
那麼我們可以理解java當中的取鎖其實可以理解是給物件上鎖,也就是改變物件頭的狀態,如果上鎖成功則進入同步代碼塊。
但是java當中的鎖有分為很多種,從上圖可以看出大體分為偏向鎖、輕量鎖、重量鎖三種鎖狀態
 
那麼這三種鎖的原理是什麼? 所以我們需要先研究這個物件頭。
 
java物件的佈局以及物件頭的:
 通過JOL來分析java的物件佈局
//首先新增JOL的依賴
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>
java程式碼:
首先建立一個類:
//一個啥都沒有的類
public class DemoTest {
}
在建立一個列印java物件頭的類:
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
 
public class Demo1 {
    static DemoTest demoTest = new DemoTest();
    public static void main(String[] args) {
        System.out.println(VM.current().details());
        System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
    }
}
執行結果:
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
 
 
com.test.www.DemoTest object internals:
OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     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:
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
對應:
[Oop(Ordinary Object Pointer), boolean, byte, char, short, int, float, long, double]大小
從執行結果可以分析出一個空的物件為16Byte,其中物件頭 (object header)   佔12Byte,剩下的為對齊位元組佔4Byte(也叫對齊填充,jvm規定物件頭部分必須是 8 位元組的倍數); 由於這個物件沒有任何欄位,所以之前說的物件例項是沒有的(0 Byte);
引申出兩個問題?
1.什麼叫做物件的例項資料
2.物件頭 (object header)裡面的12Byte到底是什麼?
首先要明白物件的例項資料很簡單,我們可以在DemoTest當中新增一個boolean的欄位,boolean欄位佔1byte,然後執行看結果
DemoTest.java:
//有一個boolean欄位的類
public class DemoTest {
    //佔1byte的boolean
    boolean flag = false;
}
執行結果:
# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
 
 
com.test.www.DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
分析結果:
整個物件的大小沒有改變還是一共16Byte,其中物件頭 (object header)   佔12Byte,boolean 欄位 DemoTest.flag(物件的例項資料)佔1Byte,剩下的3Byte為對齊子節(對齊填充);
由此我們可以認為一個物件的佈局大體分為三個部分分別是:物件頭(object header)、物件的例項資料、對齊位元組(對齊填充
接下來討論第二個問題物件頭 (object header)裡面的12Byte到底是什麼?為什麼是12Byte?裡面分別儲存的什麼?(不同位數的VM物件頭的長度不一樣,這裡指的是64bits的VM)
關於openjdk中物件頭的一些專業術語:http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html
首先引用openjdk文件中對物件頭的解釋:
object header
Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object's layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format.
上述引用中提到了一個java物件頭包含了2個word,並且包含了堆物件的佈局、型別、GC狀態、同步狀態和標識雜湊碼,但是具體是怎麼包含的呢?又是哪兩個word呢?請繼續看openjdk的文件:
mark word:
mark word
The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.
mark word為第一個word根據文件可以知道他裡面包含了鎖的資訊,hashcode,gc資訊等等
 
klass pointer:
klass pointer
The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original object. For Java objects, the "klass" contains a C++ style "vtable".
kclass word為第二個word根據文件可以知道這個主要指向物件的後設資料
 
 
|======================================================================================================================|
|                                                     object header                                                    |
|======================================================================================================================|
|                 mark word                                 |                    klass word                            |
|======================================================================================================================|
假設我們理解一個物件主要由上圖兩部分組成(陣列物件除外,陣列物件的物件頭還包含一個陣列長度),
那麼一個物件頭(object header)是多大呢?
我們從hotspot(jvm)的原始碼註釋中得知一個mark word是一個64bits(原始碼:Mark Word(64bits)  ),那麼klass的長度是多少呢?
所以我們需要想辦法來獲得java物件頭的詳細資訊,驗證一下他的大小,驗證一下里麵包含的資訊是否正確。
根據上述JOL列印的物件頭資訊可以知道一個物件頭(object header)是12Byte(96bits),而JVM原始碼中:Mark Word為8Byte(64bits),可以得出  klass是4Byte(32bits)【jvm預設開啟了指標壓縮:壓縮:4Byte(32bits);不壓縮:8byte(64bits)
和鎖相關的就是mark word了,接下來重點分析mark word裡面資訊
根據hotspot(jvm)的原始碼註釋中得知在無鎖的情況下mark word當中的前56bits存的是物件的hashcode(unused:25 + hash:31 = 56 bits--> hashcode);
那麼來驗證一下:
java程式碼:
public class DemoTest {
    //佔1byte的boolean
    boolean flag = false;
}
 
public class Demo1 {
    static DemoTest demoTest = new DemoTest();
    public static void main(String[] args) {
        System.out.println("befor hash");
        //沒有計算HASHCODE之前的物件頭
        System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
 
        //JVM 計算的hashcode 轉換為16進位制
        System.out.println("//計算完hashcode 轉為16進位制:");
        System.out.println("jvm hashcode------------0x"+Integer.toHexString(demoTest.hashCode()));
 
        //當計算完hashcode之後,我們可以檢視物件頭的資訊變化
        System.out.println("after hash");
        System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
    }
}
執行結果:
befor hash
# WARNING: Unable to attach Serviceability Agent. You can try again with escalated privileges. Two options: a) use -Djol.tryWithSudo=true to try with sudo; b) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
com.test.www.DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
 
//計算完hashcode 轉為16進位制:
jvm hashcode------------0xe6ea0c6
 
after hash
com.test.www.DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 c6 a0 6e (00000001 11000110 10100000 01101110) (1856030209)
      4     4           (object header)                           0e 00 00 00 (00001110 00000000 00000000 00000000) (14)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
根據執行結果就會發現:
befor hash之前:
00000001 00000000 00000000 00000000 00000000 00000000 00000000 00000000
after hash(計算完hashcode之後):
00000001 11000110 10100000 01101110 00001110 00000000 00000000 00000000
根據hotspot(jvm)的原始碼註釋中得知在無鎖的情況下mark word當中的前56bits存的是物件的hashcode(unused:25 + hash:31 = 56 bits--> hashcode)得知:
befor hash之前:
00000001 (00000000 00000000 00000000 00000000 00000000 00000000 00000000
after hash(計算完hashcode之後):
00000001 (11000110 10100000 01101110 00001110 00000000 00000000 00000000
()括號中的也就是高亮部分為mark word的前56bits的hashcode
也可以這樣說:在befor hash之前,是沒有進行hashcode之前的物件頭資訊,可以看出標號為2-8的56bits是沒有值的:
    1            2            3            4        5            6            7            8
00000001     00000000     00000000     00000000   00000000    00000000     00000000     00000000
但是在計算完hashcode之後就有值了:
   1            2            3            4          5            6            7            8
00000001   11000110       10100000    01101110   00001110     00000000    00000000     00000000
就可以確定java物件頭當中的mark word裡面的後七個位元組儲存是hashcode資訊
那我們先來分析下計算完的hashcode,看與我們轉換完的16進位制是否相符?
計算完hashcode之後(標號為2-8的):
11000110 10100000 01101110 00001110 00000000 00000000 00000000
這裡涉及到大小端相關知識(自行掃盲):
大端模式,是指資料的高位元組儲存在記憶體的低地址中,而資料的低位元組儲存在記憶體的高地址中,這樣的儲存模式有點兒類似於把資料當作字串順序處理:地址由小向大增加,而資料從高位往低位放;這和我們的閱讀習慣一致。
小端模式,是指資料的高位元組儲存在記憶體的高地址中,而資料的低位元組儲存在記憶體的低地址中,這種儲存模式將地址的高低和資料位權有效地結合起來,高地址部分權值高,低地址部分權值低。
一般在網路中用的大端;本地用的小端;
也就是我們分在分析計算完的hashcode是否與16進位制相符應當採用下面的方法:
 
16進位制標號         1  2  3  4
jvm------------0x e 6e a0 c6
 
對應16進位制的標號                                4        3        2
                                             c6       a0       6e
0 4 (object header) 01 c6 a0 6e (00000001 11000110 10100000 01101110) (1856030209)
 
對應16進位制的標號                      1
                                    e        0         0        0          (出現0的情況16進位制忽略不顯示)
4 4 (object header) 0e 00 00 00 (00001110 00000000 00000000 00000000) (14)
 
注意:此處16進位制標的標號是我本人打標識,是為了方便理解大小端的含義
線上進位制轉換工具:https://www.sojson.com/hexconvert.html
java物件頭當中的mark word裡面的第1個位元組( 00000001   )中儲存的分別是:
|======================================================================================================================|
|                                                     00000001                                                         |
|======================================================================================================================|
|                                  unused:1 |  age:4 | biased_lock:1 | lock:2                                          |
|======================================================================================================================|
|                                    0     |   0000  |      0        |     01                                          |
|======================================================================================================================|
|                                   未使用 | GC分代年齡|   偏向鎖標識    | 物件的狀態                                       |
|======================================================================================================================|
關於物件狀態一共分為五種狀態,分別是無鎖、偏向鎖、輕量鎖、重量鎖、GC標記
那麼2bit,如何能表示五種狀態(2bit最多隻能表示4中狀態分別是:00,01,10,11)
jvm做的比較好的是把偏向鎖和無鎖狀態表示為同一個狀態,然後根據圖中偏向鎖的標識再去標識是無鎖還是偏向鎖狀態
題外話:4位的Java物件年齡。在GC中,如果物件在Survivor區複製一次,年齡增加1。當物件達到設定的閾值時,將會晉升到老年代。預設情況下,並行GC的年齡閾值為15,併發GC的年齡閾值為16。由於age只有4位,所以最大值為15,這就是-XX:MaxTenuringThreshold選項最大值為15的原因
什麼意思呢?寫個程式碼分析一下,在寫程式碼之前我們先記得無鎖狀態下的資訊為00000001,其中偏向鎖標識為: 0, 此時物件的狀態為 01 然後寫一個偏向鎖的例子看看結果:
java程式碼:
class DemoTest{
    boolean flag = false;
}
public class Demo1 {
    static DemoTest demoTest;
    public static void main(String[] args) {
        demoTest = new DemoTest();
        System.out.println("befor lock");
        System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
 
        //加鎖
        sysn();
 
        System.out.println("after lock");
        System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
    }
 
 
    public static void sysn(){
        synchronized (demoTest){
       System.out.println("lock ing")
           System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
        }
    }
}
執行結果:
befor lock
com.test.www.Demo1$DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
 
lock ing
com.test.www.Demo1$DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (10101000 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

 

after lock
com.test.www.Demo1$DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
分析結果:
上述程式碼只有一個執行緒去呼叫sysn()方法;故而講道理應該是偏向鎖,但是你發現輸出的效果(第一個位元組)依然是:
befor lock
00000001
 
lock ing
10101000
 
after lock
00000001
wocao!!!居然是0 00 不是1 01,為啥會出現這種情況呢?
經過翻hotspot原始碼發現:
路徑: openjdk/hotspot/src/share/vm/runtime/globals.hpp
 
product(bool, UseBiasedLocking, true,                                     \
        "Enable biased locking in JVM")                                   \
                                                                          \
product(intx, BiasedLockingStartupDelay, 4000,                            \
        "Number of milliseconds to wait before enabling biased locking")  \
        range(0, (intx)(max_jint-(max_jint%PeriodicTask::interval_gran))) \
        constraint(BiasedLockingStartupDelayFunc,AfterErgo)               \
BiasedLockingStartupDelay, 4000  //偏向鎖延遲4000ms
這段話的意思是:虛擬機器在啟動的時候對於偏向鎖有延遲,延遲是4000ms
現在我們來驗證一下再執行程式碼之前先給主線睡眠5000ms再來看下結果:
class DemoTest{
    boolean flag = false;
}
public class Demo1 {
    static DemoTest demoTest;
    public static void main(String[] args) {
 
        //睡眠5000ms
        Thread.sleep(5000);        
 
        demoTest = new DemoTest();
        System.out.println("befor lock");
        System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
 
 
        //加鎖
        sysn();
 
 
        System.out.println("after lock");
        System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
    }
 
 
   public static void sysn(){
        synchronized (demoTest){
       System.out.println("lock ing")
           System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
        }
    }
}
執行結果:
befor lock
com.test.www.Demo1$DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
 
 
lock ing
com.test.www.Demo1$DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 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)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
 
 
after lock
com.test.www.Demo1$DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 48 80 74 (00000101 01001000 10000000 01110100) (1954564101)
      4     4           (object header)                           e2 7f 00 00 (11100010 01111111 00000000 00000000) (32738)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
我們就會發現befor和ing完全一樣了(說明jvm預設自動給加偏向鎖了):
befor lock
00000101
 
lock ing
00000101
 
after lock
00000101
分析00000101一下:
|======================================================================================================================|
|                                                     00000101                                                         |
|======================================================================================================================|
|                                  unused:1 |  age:4 | biased_lock:1 | lock:2                                          |
|======================================================================================================================|
|                                    0     |   0000  |      1        |     01                                          |
|======================================================================================================================|
|                                   未使用 | GC分代年齡|   偏向鎖標識    | 物件的狀態                                       |
|======================================================================================================================|
如圖所示:之前的 0 變成了1 說明偏向鎖的biased_lock狀態已經啟用了,偏向鎖標識為: 1  此時物件的狀態為 01 ;需要注意的是after lock,退出同步後依然保持了偏向資訊;
想想為什麼偏向鎖會延遲?
因為jvm 在啟動的時候需要載入資源,這些物件加上偏向鎖沒有任何意義啊,減少了大量偏向鎖撤銷的成本;所以預設就把偏向鎖延遲了4000ms;
經過翻hotspot原始碼發現:
路徑:openjdk/hotspot/src/share/vm/runtime/biasedLocking.cpp
void BiasedLocking::init() {
  // If biased locking is enabled, schedule a task to fire a few
  // seconds into the run which turns on biased locking for all
  // currently loaded classes as well as future ones. This is a
  // workaround for startup time regressions due to a large number of
  // safepoints being taken during VM startup for bias revocation.
  // Ideally we would have a lower cost for individual bias revocation
  // and not need a mechanism like this.
  if (UseBiasedLocking) {
    if (BiasedLockingStartupDelay > 0) {
      EnableBiasedLockingTask* task = new EnableBiasedLockingTask(BiasedLockingStartupDelay);
      task->enroll();
    } else {
      VM_EnableBiasedLocking op(false);
      VMThread::execute(&op);
    }
  }
}
英文大概翻譯為: 當jvm啟動記載資源的時候,初始化的物件加偏向鎖會耗費資源,減少大量偏向鎖撤銷的成本(jvm的偏向鎖的優化)
這就解釋了加上睡眠5000ms,偏向鎖就會出現;為了方便我們測試我們可以直接通過修改jvm的引數來禁止偏向鎖延遲(不用在程式碼睡眠了):
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
到這裡就通過物件有解析成hashcode驗證了鎖的狀態為偏向鎖:1 01
接下來我們來分析輕量級鎖(注意在不禁止延遲偏向鎖的情況下驗證):
java程式碼:
static class DemoTest{
    boolean flag = false;
}
public class Demo1 {
    static DemoTest demoTest;
    public static void main(String[] args) throws InterruptedException {
        demoTest = new DemoTest();
        System.out.println("befor lock");
 
 
        System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
 
 
        //加鎖
        sysn();
 
 
        System.out.println("after lock");
        System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
    }
 
 
    public static void sysn(){
        synchronized (demoTest){
            System.out.println("lock ing");
            System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
        }
    }
}
執行結果:
befor lock
com.test.www.Demo1$DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
 
 
lock ing
com.test.www.Demo1$DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           a8 78 5b 03 (10101000 01111000 01011011 00000011) (56326312)
      4     4           (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
 
 
after lock
com.test.www.Demo1$DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
分析結果:
befor lock
00000001
 
lock ing
10101000
 
after lock
00000001
通過分析lock ing結果可以看出:
|======================================================================================================================|
|                                                  10101000                                                            |
|======================================================================================================================|
|                                        ptr_to_lock_record:62 | lock:2                                                |
|======================================================================================================================|
|                                               101010         | 00                                                    |
|======================================================================================================================|
|                                         指向棧中鎖記錄的指標    | 物件的狀態                                             |
|======================================================================================================================|
就可以看出輕量級鎖物件的狀態為  00
 
接下來我們來分析重量級鎖(注意在不禁止延遲偏向鎖的情況下驗證):
java程式碼:
class DemoTest {
    boolean flag = false;
}
public class Demo1 {
    static DemoTest demoTest;
 
 
    public static void main(String[] args) throws InterruptedException {
        demoTest = new DemoTest();
        System.out.println("befor lock");
        System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
 
 
        Thread t1 = new Thread() {
            @Override
            public void run() {
                synchronized (demoTest) {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
 
 
        t1.start();
        System.out.println("t1 lock ing");
        System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
 
 
        sysn();
 
 
        System.out.println("after lock");
        System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
 
 
        System.gc();
        System.out.println("after gc");
        System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
 
    }
 
 
    public static void sysn() {
        synchronized (demoTest) {
            System.out.println("main lock ing");
            System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
        }
    }
 
}
執行結果:
befor lock
com.test.www.Demo1$DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           9f c1 00 f8 (10011111 11000001 00000000 11111000) (-134168161)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
 
 
t1 lock ing
com.test.www.Demo1$DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           10 09 43 10 (00010000 00001001 01000011 00010000) (272828688)
      4     4           (object header)                           00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
      8     4           (object header)                           9f c1 00 f8 (10011111 11000001 00000000 11111000) (-134168161)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
 
 
main lock ing
com.test.www.Demo1$DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           4a e1 00 5d (01001010 11100001 00000000 01011101) (1560338762)
      4     4           (object header)                           eb 7f 00 00 (11101011 01111111 00000000 00000000) (32747)
      8     4           (object header)                           9f c1 00 f8 (10011111 11000001 00000000 11111000) (-134168161)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
 
 
after lock
com.test.www.Demo1$DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           4a e1 00 5d (01001010 11100001 00000000 01011101) (1560338762)
      4     4           (object header)                           eb 7f 00 00 (11101011 01111111 00000000 00000000) (32747)
      8     4           (object header)                           9f c1 00 f8 (10011111 11000001 00000000 11111000) (-134168161)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
 
 
after gc
com.test.www.Demo1$DemoTest object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           09 00 00 00 (00001001 00000000 00000000 00000000) (9)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           9f c1 00 f8 (10011111 11000001 00000000 11111000) (-134168161)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
分析結果:
befor lock
00000001  //無鎖
 
t1 lock ing
00010000  //輕量級鎖
 
main lock ing
01001010  //重量級鎖
 
after lock
01001010 //重量級鎖
 
after gc
00001001 //gc回收變無鎖(就會發現gc回收過一次之後 0000 變成了 0001 年齡+1了)
通過分析main lock ing結果可以看出:
|======================================================================================================================|
|                                                 01001010                                                             |
|======================================================================================================================|
|                                     ptr_to_heavyweight_monitor:62 | lock:2                                           |
|======================================================================================================================|
|                                             010010                | 10                                               |
|======================================================================================================================|
|                                        向管程Monitor的指標          |  物件的狀態                                       |
|======================================================================================================================|
就可以看出重量級鎖物件的狀態為  10
但是你會發現在after lock之後還是重量級鎖,是因為重量級鎖釋放會有延遲,可以在sync()方法中加入睡眠:
 public static void sysn() throws InterruptedException {
        synchronized (demoTest) {
            System.out.println("main lock ing");
            System.out.println(ClassLayout.parseInstance(demoTest).toPrintable());
        }
        Thread.sleep(5000);
    }

就可以看到after之後的狀態為0 01 無鎖的狀態:

after lock
com.test.www.DemoTest object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           9f c1 00 f8 (10011111 11000001 00000000 11111000) (-134168161)
     12     1   boolean DemoTest.flag                             false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
 
此時我們到這裡就已經通過分析java物件頭找出鎖的物件的狀態:
 
|======================================================================================================================|
|                          鎖的狀態                偏向鎖標識                        物件的狀態                                        
|======================================================================================================================|
|                           無鎖                     0                                01    
|======================================================================================================================|
|                           偏向鎖                   1                                01
|======================================================================================================================|
|                          輕量級鎖                                                   00             
|======================================================================================================================|
|                          重量級鎖                                                   10    
|======================================================================================================================|
|           GC(此處age:0000變為0001;每被gc掉用一次年齡回加1)                           01  
|======================================================================================================================|
 
 
 轉載請標明出處

相關文章