java并发笔记之synchronized 偏向锁 轻量级锁 重量级锁证明

简介: java并发笔记之synchronized 偏向锁 轻量级锁 重量级锁证明本篇将从hotspot源码(64 bits)入手,通过分析java对象头引申出锁的状态;本文采用大量实例及分析,请耐心看完,谢谢先来看一下hotspot的源码当中的对象头的注释(32bits 可以忽略了,现在基本没有32位...

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

相关文章
|
14天前
|
Java 开发工具 Android开发
Kotlin语法笔记(26) -Kotlin 与 Java 共存(1)
本系列教程笔记详细讲解了Kotlin语法,适合需要深入了解Kotlin的开发者。若需快速学习Kotlin,建议查看“简洁”系列教程。本期重点介绍了Kotlin与Java的共存方式,包括属性、单例对象、默认参数方法、包方法、扩展方法以及内部类和成员的互操作性。通过这些内容,帮助你在项目中更好地结合使用这两种语言。
29 1
|
15天前
|
Java 开发工具 Android开发
Kotlin语法笔记(26) -Kotlin 与 Java 共存(1)
Kotlin语法笔记(26) -Kotlin 与 Java 共存(1)
26 2
|
1天前
|
缓存 Java
java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁
本文介绍了几种常见的锁机制,包括公平锁与非公平锁、可重入锁与不可重入锁、自旋锁以及读写锁和互斥锁。公平锁按申请顺序分配锁,而非公平锁允许插队。可重入锁允许线程多次获取同一锁,避免死锁。自旋锁通过循环尝试获取锁,减少上下文切换开销。读写锁区分读锁和写锁,提高并发性能。文章还提供了相关代码示例,帮助理解这些锁的实现和使用场景。
java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁
|
2天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
4天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
19 4
|
5天前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
|
5天前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
|
5天前
|
Java 编译器 Android开发
Kotlin教程笔记(28) -Kotlin 与 Java 混编
Kotlin教程笔记(28) -Kotlin 与 Java 混编
|
7天前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
20 2
|
14天前
|
Java 编译器 Android开发
Kotlin语法笔记(28) -Kotlin 与 Java 混编
本系列教程详细讲解了Kotlin语法,适合需要深入了解Kotlin的开发者。对于希望快速学习Kotlin的用户,推荐查看“简洁”系列教程。本文档重点介绍了Kotlin与Java混编的技巧,包括代码转换、类调用、ProGuard问题、Android library开发建议以及在Kotlin和Java之间互相调用的方法。
13 1