警告⚠️:本文耗时很长,先做好心理准备

本篇将从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  
|======================================================================================================================|
 
 
 原创不易,转载请标明出处,谢谢

java并发笔记之synchronized 偏向锁 轻量级锁 重量级锁证明的更多相关文章

  1. java并发笔记之四synchronized 锁的膨胀过程(锁的升级过程)深入剖析

    警告⚠️:本文耗时很长,先做好心理准备,建议PC端浏览器浏览效果更佳. 本篇我们讲通过大量实例代码及hotspot源码分析偏向锁(批量重偏向.批量撤销).轻量级锁.重量级锁及锁的膨胀过程(也就是锁的升 ...

  2. Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  3. 【转】Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)

     一.重量级锁 上篇文章中向大家介绍了Synchronized的用法及其实现的原理.现在我们应该知道,Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的.但是监视器锁本 ...

  4. java并发笔记之证明 synchronized锁 是否真实存在

    警告⚠️:本文耗时很长,先做好心理准备 证明:偏向锁.轻量级锁.重量级锁真实存在 由[java并发笔记之java线程模型]链接: https://www.cnblogs.com/yuhangwang/ ...

  5. Java并发编程:synchronized和锁优化

    1. 使用方法 synchronized 是 java 中最常用的保证线程安全的方式,synchronized 的作用主要有三方面: 确保线程互斥的访问代码块,同一时刻只有一个方法可以进入到临界区 保 ...

  6. Java并发编程:Synchronized及其实现原理

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  7. Java并发编程原理与实战四十二:锁与volatile的内存语义

    锁与volatile的内存语义 1.锁的内存语义 2.volatile内存语义 3.synchronized内存语义 4.Lock与synchronized的区别 5.ReentrantLock源码实 ...

  8. 【死磕Java并发】-----深入分析synchronized的实现原理

    记得刚刚開始学习Java的时候.一遇到多线程情况就是synchronized.相对于当时的我们来说synchronized是这么的奇妙而又强大,那个时候我们赋予它一个名字"同步". ...

  9. 【Java并发编程】synchronized相关面试题总结

    目录 说说自己对于synchronized关键字的了解 synchronized关键字的三种使用 synchronized关键字的底层原理 JDK1.6之后对synchronized关键字进行的优化 ...

随机推荐

  1. IOS 数据存储(NSKeyedArchiver 归档篇)

    什么是归档 当遇到有结构有组织的数据时,比如字典,数组,自定义的对象等在存储时需要转换为字节流NSData类型数据,再通过写入文件来进行存储. 归档的作用 之前将数据存储到本地,只能是字符串.数组.字 ...

  2. UI-grid 表格内容可编辑(enableCellEdit可指定列编辑)

    在网上搜索了很多关于UI-Grid的问题 很遗憾好少啊啊啊 不过有API还是比较欣慰的 官方API:UI Grid 还有一位大佬的翻译的中文API:angularjs ui-grid中文api 行编辑 ...

  3. Spring Framework 组件注册 之 @Component

    Spring Framework 组件注册 之 @Component 写在前面 在spring大行其道的今天,对于spring的使用和掌握乃是不可缺少的必备技能.但是spring的整个体系尤为庞大,对 ...

  4. HBase 学习之路(十)—— HBase的SQL中间层 Phoenix

    一.Phoenix简介 Phoenix是HBase的开源SQL中间层,它允许你使用标准JDBC的方式来操作HBase上的数据.在Phoenix之前,如果你要访问HBase,只能调用它的Java API ...

  5. string类总结第二部分实战练习

    第二部门:实战练习 昨天由于时间原因,这个部分应该在同一个文章中的,无奈只能今天再开一个了,今天主要是讲一些面试题 一:equals和==的区别 最简单的面试题,也是最基础的,我估计每个学习java的 ...

  6. 【ubuntu】软件安装与apt-get下载软件的存放位置

    系统:Ubuntu16.04 常用的软件安装方式有两种: 第一种:apt-get(安装后略类似于windows中的安装版软件): 例:apt-get install ssh 1.下载的软件存放位置 / ...

  7. ES6_07_Symbol属性

    Symbol属性: 前言:ES5中对象的属性名都是字符串,容易造成重名,污染环境 Symbol: 概念:ES6中的添加了一种原始数据类型symbol(已有的原始数据类型:String, Number, ...

  8. 图解kafka - 设计原理解析

    什么是消息队列? 简单来说,消息队列是存放消息的容器.客户端可以将消息发送到消息服务器,也可以从消息服务器获取消息. 问题导读: ********* 为什么需要消息系统? kafka架构? kafka ...

  9. mysql 终端命令

    1.打开数据库 /usr/local/MySQL/bin/mysql -u root -p 2.输入root密码 3.使用我的数据库 use mysql 4.查看表 desc table_name 5 ...

  10. django基础知识之认识MVT MVC:

    MVT Django是一款python的web开发框架 与MVC有所不同,属于MVT框架 m表示model,负责与数据库交互 v表示view,是核心,负责接收请求.获取数据.返回结果 t表示templ ...