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

  • 相关阅读:
    grunt学习(二)——安装grunt及其插件
    grunt学习(一)——nodejs入门
    18个常用的网站性能测试工具
    JSTL(fn函数)
    为MySQL选择合适的备份方式
    Web开发者文档和手册
    为Ruby On Rails开发者准备的5款IDE
    量子统计
    【日常训练】【ACM】2019-10-27_ccpc2019秦皇岛
    poj 2686 Traveling by Stagecoach
  • 原文地址:https://www.cnblogs.com/ccsert/p/12381817.html
Copyright © 2020-2023  润新知