synchronized锁升级和mardword详解

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++代码了这触及了我的知识盲区。

posted @ 2020-02-29 10:48  ccsert  阅读(603)  评论(0编辑  收藏  举报