关于synchronized:验证synchronized锁升级时对象头变化全过程-springboot实战电商项目mall4j

42次阅读

共计 5808 个字符,预计需要花费 15 分钟才能阅读完成。

springboot 实战电商我的项目 mall4j(https://gitee.com/gz-yami/mall4j)

java 开源商城零碎

验证 synchronized 锁降级时对象头变动全过程

jdk 版本:1.8

零碎:window10 64 位

jvm 启动参数:-XX:BiasedLockingStartupDelay=0(勾销提早加载偏差锁)


首先须要已知几个概念

  1. java 非数组对象(一般对象)的内存构造

​ 如果是 array 对象,则会再占用一个 length 空间(4 字节),记录数组的长度。

  1. java object 的 markword 格局(64 位虚拟机上)

3.synchronized 四锁降级流程以及何时降级(不赘述)


通过理论编码查看别离在这四个锁状态时,锁标记位是否相应变动

引入 jol 工具包

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

无锁态

@Test
public void test01() throws Exception {Object o = new Object();
    System.out.println(ClassLayout.parseInstance(o).toPrintable());
}

执行后果

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 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)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

第一、二行的 object header 是 markword 的内存,第三行的 object header 是 class pointer 的内容,第四行是对齐填充。

只需关注第一、二行。

第一、二行因为是由低位打印到高位的,所以须要反过来看才会和上方的锁状态表格中一一对应。

即 <u>00000000 00000000 00000000 00000000 00000000 00000000 000000</u>00 00000101

对照表格,重点最初三位,是否偏差锁为 1,锁标记位为 01,实践上来说偏差锁标记应该为 0,这是因为咱们加了个 勾销提早加载偏差锁的启动参数导致的,如果把启动参数去掉,那么偏差锁标记位就是 0。

JVM 启动时会进行一系列的简单流动,比方装载配置,零碎类初始化等等。在这个过程中会应用大量 synchronized 关键字对对象加锁,且这些锁大多数都不是偏差锁。为了缩小初始化工夫,JVM 默认延时加载偏差锁,而咱们把它禁用掉了,所以偏差锁标记位就变成了 1。

即便如此,仍旧能够看出下划线局部为偏差线程的 id 存储地位,目前全为 0,也即这个对象目前不偏差任何线程,所以以后对象还是可偏差的状态

即以后对象锁状态为无锁态得证。

偏差锁态

@Test
public void test2() throws Exception {Object o = new Object();
    synchronized (o) {}
    System.out.println(ClassLayout.parseInstance(o).toPrintable());
}

执行后果

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           05 e8 d1 02 (00000101 11101000 11010001 00000010) (47310853)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

一样先把前两行按表格中的程序先排列好,如下:

<u>00000000 00000000 00000000 00000000 00000010 11010001 111010</u>00 00000101

这回能够看出偏差锁标记位 1,锁标记位为 01,而且偏差锁的线程 id 也被占用了,所以显然该对象线程是偏差锁态。

同时,从代码中看出,是先对 o 上 synchronized 锁且锁开释之后才对 o 进行打印的,能够得出一个论断,偏差锁状态不会被动撤销,而是会持续保留其状态。

轻量级锁

@Test
public void test3() throws Exception {Object o = new Object();
    synchronized (o) {}
    new Thread(() -> {synchronized (o) {System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }).start();
    TimeUnit.SECONDS.sleep(10);
}

执行后果

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           00 ef a5 1e (00000000 11101111 10100101 00011110) (514191104)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

00000000 00000000 00000000 00000000 00011110 10100101 11101111 00000000

对照表,显然此时的 o 处于轻量级锁态。因为主线程先对 o 上锁,o 处于偏差锁,而后再来个线程对 o 上锁,上锁前就偏差锁就会收缩为轻量级锁。

综上,得证。

重量级锁

@Test
public void test4() throws Exception {Object o = new Object();
    AtomicInteger index = new AtomicInteger(0);
    for (int i = 0; i < 10; i++) {new Thread(() -> {synchronized (o) {index.getAndIncrement();
                if(index.get() == 9)
                System.out.println(ClassLayout.parseInstance(o).toPrintable());
            }
        }).start();}
    TimeUnit.SECONDS.sleep(10);
}

执行后果

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           fa 05 d7 1c (11111010 00000101 11010111 00011100) (483853818)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

00000000 00000000 00000000 00000000 00011100 11010111 00000101 11111010

呈现大量锁竞争,实践上 o 该当处于重量级锁态,对照表格显然得出是 o 处于重量级锁态,得证。


再来看如下两种状况

  1. 在对 o 上锁前计算 o 的 hashcode,上锁时 o 处于什么状态

    @Test
    public void test5() throws Exception {Object o = new Object();
        o.hashCode();
        synchronized (o) {System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }
    

从实践上将应该还是偏差锁,那么看执行后果。

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           b8 e4 ac 02 (10111000 11100100 10101100 00000010) (44885176)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

此时的锁标记位为 00,为轻量级锁,并不是偏差锁。

论断:计算过 hashcode 的对象,在上锁后会间接收缩为轻量级锁,跳过偏差锁。

  1. 在对 o 上锁后计算 o 的 hashcode,此时的 o 处于什么状态

    @Test
    public void test6() throws Exception {Object o = new Object();
        synchronized (o) {o.hashCode();
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }
    

实践上这里的 o 应该是偏差锁。看执行后果:

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           ba c7 52 1c (10111010 11000111 01010010 00011100) (475187130)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

此时的锁标记位为 10,为重量级锁,并不是偏差锁。

论断:处于偏差锁态的对象只有计算过 hashcode,会间接收缩为重量级锁。

拓展:

测验代码中计算出来的 hashcode 与 markword 中对应记录 hashcode 中的值统一

@Test
public void test7() throws Exception {Object o = new Object();
    System.out.println(o.hashCode());
    System.out.println(ClassLayout.parseInstance(o).toPrintable());
}

输入后果:

1174290147
java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 e3 3e fe (00000001 11100011 00111110 11111110) (-29433087)
      4     4        (object header)                           45 00 00 00 (01000101 00000000 00000000 00000000) (69)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

将 markword 缮写进去,即 00000000 00000000 00000000 01000101 11111110 00111110 11100011 00000001

对照文章结尾的表格,可知从第 26 位开始到底 56 位都是记录 hashcode 的比特位。即 1000101 11111110 00111110 11100011,将其转为十进制,后果为 1,174,290,147。和代码中输入的后果统一。

springboot 实战电商我的项目 mall4j(https://gitee.com/gz-yami/mall4j)

java 开源商城零碎

正文完
 0