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

相关文章
|
1天前
|
安全 Java 调度
Java并发编程:深入理解线程与锁
【4月更文挑战第18天】本文探讨了Java中的线程和锁机制,包括线程的创建(通过Thread类、Runnable接口或Callable/Future)及其生命周期。Java提供多种锁机制,如`synchronized`关键字、ReentrantLock和ReadWriteLock,以确保并发访问共享资源的安全。此外,文章还介绍了高级并发工具,如Semaphore(控制并发线程数)、CountDownLatch(线程间等待)和CyclicBarrier(同步多个线程)。掌握这些知识对于编写高效、正确的并发程序至关重要。
|
2天前
|
Java
浅谈Java的synchronized 锁以及synchronized 的锁升级
浅谈Java的synchronized 锁以及synchronized 的锁升级
7 0
|
2天前
|
Java 开发者
Java中多线程并发控制的实现与优化
【4月更文挑战第17天】 在现代软件开发中,多线程编程已成为提升应用性能和响应能力的关键手段。特别是在Java语言中,由于其平台无关性和强大的运行时环境,多线程技术的应用尤为广泛。本文将深入探讨Java多线程的并发控制机制,包括基本的同步方法、死锁问题以及高级并发工具如java.util.concurrent包的使用。通过分析多线程环境下的竞态条件、资源争夺和线程协调问题,我们提出了一系列实现和优化策略,旨在帮助开发者构建更加健壮、高效的多线程应用。
3 0
|
3天前
|
存储 缓存 安全
Java并发基础之互斥同步、非阻塞同步、指令重排与volatile
在Java中,多线程编程常常涉及到共享数据的访问,这时候就需要考虑线程安全问题。Java提供了多种机制来实现线程安全,其中包括互斥同步(Mutex Synchronization)、非阻塞同步(Non-blocking Synchronization)、以及volatile关键字等。 互斥同步(Mutex Synchronization) 互斥同步是一种基本的同步手段,它要求在任何时刻,只有一个线程可以执行某个方法或某个代码块,其他线程必须等待。Java中的synchronized关键字就是实现互斥同步的常用手段。当一个线程进入一个synchronized方法或代码块时,它需要先获得锁,如果
21 0
|
4天前
|
存储 缓存 Java
线程同步的艺术:探索 JAVA 主流锁的奥秘
本文介绍了 Java 中的锁机制,包括悲观锁与乐观锁的并发策略。悲观锁假设多线程环境下数据冲突频繁,访问前先加锁,如 `synchronized` 和 `ReentrantLock`。乐观锁则在访问资源前不加锁,通过版本号或 CAS 机制保证数据一致性,适用于冲突少的场景。锁的获取失败时,线程可以选择阻塞(如自旋锁、适应性自旋锁)或不阻塞(如无锁、偏向锁、轻量级锁、重量级锁)。此外,还讨论了公平锁与非公平锁,以及可重入锁与非可重入锁的特性。最后,提到了共享锁(读锁)和排他锁(写锁)的概念,适用于不同类型的并发访问需求。
35 2
|
5天前
|
Java 程序员 编译器
Java中的线程同步与锁优化策略
【4月更文挑战第14天】在多线程编程中,线程同步是确保数据一致性和程序正确性的关键。Java提供了多种机制来实现线程同步,其中最常用的是synchronized关键字和Lock接口。本文将深入探讨Java中的线程同步问题,并分析如何通过锁优化策略提高程序性能。我们将首先介绍线程同步的基本概念,然后详细讨论synchronized和Lock的使用及优缺点,最后探讨一些锁优化技巧,如锁粗化、锁消除和读写锁等。
|
Java Spring 容器
java轻量级IOC框架Guice
Guice是由Google大牛Bob lee开发的一款绝对轻量级的java IoC容器。其优势在于: 速度快,号称比spring快100倍。 无外部配置(如需要使用外部可以可以选用Guice的扩展包),完全基于annotation特性,支持重构,代码静态检查。
1649 0
|
9天前
|
算法 Java 开发者
Java中的多线程编程:概念、实现与性能优化
【4月更文挑战第9天】在Java编程中,多线程是一种强大的工具,它允许开发者创建并发执行的程序,提高系统的响应性和吞吐量。本文将深入探讨Java多线程的核心概念,包括线程的生命周期、线程同步机制以及线程池的使用。接着,我们将展示如何通过继承Thread类和实现Runnable接口来创建线程,并讨论各自的优缺点。此外,文章还将介绍高级主题,如死锁的预防、避免和检测,以及如何使用并发集合和原子变量来提高多线程程序的性能和安全性。最后,我们将提供一些实用的性能优化技巧,帮助开发者编写出更高效、更稳定的多线程应用程序。
|
8天前
|
安全 算法 Java
深入理解Java并发编程:线程安全与性能优化
【4月更文挑战第11天】 在Java中,高效的并发编程是提升应用性能和响应能力的关键。本文将探讨Java并发的核心概念,包括线程安全、锁机制、线程池以及并发集合等,同时提供实用的编程技巧和最佳实践,帮助开发者在保证线程安全的前提下,优化程序性能。我们将通过分析常见的并发问题,如竞态条件、死锁,以及如何利用现代Java并发工具来避免这些问题,从而构建更加健壮和高效的多线程应用程序。
|
1天前
|
安全 Java
java多线程(一)(火车售票)
java多线程(一)(火车售票)