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

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:指向管程Monitor的指針 ;       lock: 對象的狀態
|===============================================================================================|=======================|

註解: 空,不需要記錄信息 ; lock: 對象的狀態
|===============================================================================================|=======================|
由上可以知道java的對象頭在對象的不同狀態下會有不同的表現形式,主要有三種狀態,無鎖狀態、加鎖狀態、gc標記狀態。
那麼我們可以理解java當中的取鎖其實可以理解是給對象上鎖,也就是改變對象頭的狀態,如果上鎖成功則進入同步代碼塊。
但是java當中的鎖有分爲很多種,從上圖可以看出大體分爲偏向鎖、輕量鎖、重量鎖三種鎖狀態。

那麼這三種鎖的原理是什麼? 所以我們需要先研究這個對象頭。

java對象的佈局以及對象頭的:
通過JOL來分析java的對象佈局
//首先添加JOL的依賴

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


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
|======================================================================================================================|

原文地址https://www.cnblogs.com/yuhangwang/p/11267364.html

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