警告⚠️:本文耗时很长,先做好心理准备
本篇将从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的依赖
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
|======================================================================================================================|
原创不易,转载请标明出处,谢谢