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


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

本篇將從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
 
注意:這塊嚴謹來說,在jdk 1.6之后,關於使用偏向鎖和輕量級鎖,jvm是有優化的,在沒有禁止偏向鎖延遲的情況下,使用的是輕量級鎖;禁止偏向鎖延遲的話,使用的是偏向鎖;
 
到這里就通過對象有解析成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  
|======================================================================================================================|
 
 
 原創不易,轉載請標明出處,謝謝


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM