synchronized锁升级的过程
在了解锁升级过程中我们还要知道Java对象的结构
Java对象由对象头
,实例数据
,填充数据
组成,我们这里主要关注对象头。
markWord对象头
对象头里的数据主要是一些运行时数据。
对象头的结构入下图
我这里使用了jol工具进行打印,这里我打印了一个空对象的对象头信息
我们来稍微分析一下,首先我们得知道这几行的含义
OFFSET:偏移量
SIZE:大小(字节)
TYPE DESCRIPTION:类型说明
VALUE:具体的数据值
这里前12个字节都是markword,而最后4个字节是用来进行对齐的,关于为什么要对齐这里又要扯到缓存一致性协议上,我们这里暂时先忽略最后四个字节,只看前面的12个字节
这里稍微提一下,在32位的机器上对象头是占用8个byte,而64位机器上占用16个byte。
为了证明我不是瞎扯淡这里我把hotspot的注释贴一下
// The markWord describes the header of an object.
//
// 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)
//
// 64 bits:
// --------
// unused:25 hash:31 -->| unused_gap:1 age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:54 epoch:2 unused_gap:1 age:4 biased_lock:1 lock:2 (biased object)
有兴趣的朋友可以看openjdk的源码
https://github.com/unofficial-openjdk/openjdk/blob/jdk/jdk/src/hotspot/share/oops/markWord.hpp
接着我们来分析一下对象头的字节信息。
01 00 00 00 (00000001 00000000 00000000 00000000) (1)
这里只有一个01,其余啥都没有???
原因是没有调用过hashcode方法,此时hashcode还没被计算过,我在调用一下hashcode方法顺便打印一下,然后转成16进制,方便我们等会做对比
00000001 11001111 01001100 00010010
16进制hash值为332ba300
先贴一段官方的注释
// [JavaThread* | epoch | age | 1 | 01] lock is biased toward given thread
// [0 | epoch | age | 1 | 01] lock is anonymously biased
//
// - the two lock bits are used to describe three states: locked/unlocked and monitor.
//
// [ptr | 00] locked ptr points to real header on stack
// [header | 0 | 01] unlocked regular object header
// [ptr | 10] monitor inflated lock (header is wapped out)
// [ptr | 11] marked used to mark an object
//
// We assume that stack/thread pointers have the lowest two bits cleared.
然后这里还有一个表格可以对照
对照上表中的信息我们可以观察到,空对象是没有上锁的,但是好像位置不太对,那是因为这里有一个大端小端的问题,我们给他翻译翻译就ok了
00000001 11001111 01001100 00010010
00010010 01001100 11001111 00000001
相对的我们把hash值也像这样对调一下就能发现完全能对的上
00 a3 2b 33
33 2b a3 00
我们给对象上锁看看,是否会和前面总结的升级为偏向锁,然后在打印对象头进行对比看看。
按照我们的推测,它应该值还是01但是会记录对象的信息,但是结果是它并不是01而是变成了00,也就是轻量级锁,为什么会这样,我们等会在讨论,我现在继续让锁升级,让代码跑的久一点它应该会因为自旋次数超出后升级为重量级锁。
升级为重量级锁看样子是没有问题的,那我们上面总结的猜想基本是正确的,但是为什么会直接从不加锁状态直接变成轻量级锁跳过了偏向锁呢?
偏向锁其实是jvm内置的一种机制,自从jdk1.6以后就默认启动。这个锁一般不需要我们来控制,一般都是由jvm自己去控制。jvm内置对偏向锁有一个延迟,所以我们自己创建的锁就直接升级为了自旋锁。
那怎么样我们才能看见偏向锁的效果呢?
我们可以修改jvm启动参数然后把延迟置为0就能看见偏向锁的效果了。
关闭延迟:
-XX:BiasedLockingStartupDelay=0
然后我在打印一下就能看见偏向锁了
可以看见又是01了。然后我们发现相较于空对象还多了些东西
我们对照上面的表格分析
前23位都是线程id,2位是epoch,4位是分代年龄,1位是确定是否为偏向锁,还有两位判断锁的标志位
我们还是将字节翻译一下
00000101 11101000 11011000 00100111
00100111 11011000 11101000 00000101
前面的可以不看了,我们看最后三位。确定了是偏向锁。
这就是synchronized 锁升级的一个基本可见的过程了,在往深处讨论就到c++代码了这触及了我的知识盲区。