前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >synchronized锁升级原理

synchronized锁升级原理

作者头像
CBeann
发布2023-12-25 18:48:35
1210
发布2023-12-25 18:48:35
举报
文章被收录于专栏:CBeann的博客CBeann的博客

Java对象的内存结构

对象内存结构

在64位操作系统下,

MarkWord(下图_mark)占64位

KlassWord(下图_klass)占32位 64位系统的Klass Word不是32位,默认64位,开启指针压缩后为32(感谢评论老哥的指出)

64位系统的Klass Word不是32位,默认64位,开启指针压缩后为32

_lengh(只有数据对象才有,不考虑)

实例数据(下图instance data)看参数的类型,int就占32位(4byte)

补齐(padding)是JVM规定java对象内存必须是8byte的倍数,如果实例数据占2byte,那么(64bit的Markword+32bit的Klassword+实例数据32bit)=128bit=16byte是8byte的倍数,所以padding部分为0。

查看对象内存结构

JDK8

代码语言:javascript
复制
 <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>
代码语言:javascript
复制
public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
  }
}

class Dog {
  char age;
}

如上图所示,对象头中的MarkWord占8byte,KlassWord占4个byte,实例属性age是char类型占2个byte,那么此时加起来为14byte,为了满足是8的倍数,要补充2个byte。

下图是当Dog对象里的age变为int时打印的结果,请自行对比。

对象头

下图是引自《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) 周志明》中的一个图,下图是32操作系统下的对象头中的Mark Word(32位),Klass Word(32位),一共是64位。

64操作系统下,Mark Word的长度是64,在加Klass Word(32位),一共是96位,其实对象头长什么样其实不是本文的重点,本文的重点是验证锁升级的过程,所以我们只需要关注对象头中Mark Word的最后3位即可,如下图中的后3位。

锁升级的过程

锁状态

25bit

4bit

1bit

2bit

23bit

2bit

是否偏向锁

锁标志位

1

无锁

对象的HashCode

分代年龄

0

01

2

无锁

对象的HashCode

分代年龄

1

01

3

偏向锁

线程ID

Epoch

分代年龄

1

01

4

轻量级锁

指向栈中锁记录的指针

00

5

重量级锁

指向重量级锁的指针

10

6

GC标记

11

前提

由于大小端引起的问题,使得这里展示的高低位相反,如下图所示,所以我要关注的就是⑧位置的最后3位足矣

在这里插入图片描述
在这里插入图片描述

无锁状态

代码语言:javascript
复制
public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
  }
}

如下图所示,001表示的无锁状态并且不允许偏向 (其实默认是开启偏向的,只不过虚拟机后在运行后几秒才开启偏向锁)

使用下面的参数,如下图所示 ,会发现状态为101,表示无锁状态

代码语言:javascript
复制
-XX:BiasedLockingStartupDelay=0

由无锁状态---->偏向锁状态

单线程访问锁的时候,锁由无锁状态变为偏向锁状态。

代码语言:javascript
复制
// -XX:BiasedLockingStartupDelay=0
public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
    //上锁
    synchronized (dog){
      System.out.println(ClassLayout.parseInstance(dog).toPrintable());
    }
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
  }
}

class Dog {
  int age;
}

如上图所示,开始状态为101,为可偏向,无锁状态

上锁后状态是101,为可偏向,有锁状态

解锁后:状态为101,为可偏向,有锁状态

区别为:当线程给无锁状态的lock加锁时,会把线程ID存储到MarkWord中,即锁偏向于该ID的线程,偏向锁不会自动释放。

上面表格中2->3的过程。

偏向锁状态---->轻量级锁状态

多线程使用锁(不竞争,错开时间访问),锁由偏向锁状态变为轻量级锁状态

代码语言:javascript
复制
// -XX:BiasedLockingStartupDelay=0
public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println("初始状态:");
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());

    new Thread(
            () -> {
              synchronized (dog) {
                System.out.println("hello world");
              }
            },
            "t1")
        .start();
    System.out.println("线程1释放锁后:");
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
    try {
      TimeUnit.SECONDS.sleep(3);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
    }
    new Thread(
            () -> {
              synchronized (dog) {
                System.out.println("线程2上锁:");
                System.out.println(ClassLayout.parseInstance(dog).toPrintable());
              }
              System.out.println("线程2释放锁:");
              System.out.println(ClassLayout.parseInstance(dog).toPrintable());
            },
            "t2")
        .start();
  }
}

class Dog {
  int age;
}

初始状态为101,为可偏向,并且为无锁状态

线程1释放锁后,状态为101,并且存储了线程ID,为偏向锁状态,偏向于线程1

线程2上锁,上锁后,状态为00,轻量级锁状态

线程2释放锁后,状态为001,此时为不可偏向的无锁状态。

重量级锁状态

代码语言:javascript
复制
// -XX:BiasedLockingStartupDelay=0
public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println("初始状态:");
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());

    new Thread(
            () -> {
              synchronized (dog) {
                System.out.println("");
                try {
                  TimeUnit.SECONDS.sleep(3);
                } catch (Exception e) {
                  e.printStackTrace();
                } finally {
                }
              }
            },
            "t1")
        .start();

    new Thread(
            () -> {
              synchronized (dog) {
                System.out.println("线程2上锁");
                System.out.println(ClassLayout.parseInstance(dog).toPrintable());
                try {
                  TimeUnit.SECONDS.sleep(3);
                } catch (Exception e) {
                  e.printStackTrace();
                } finally {
                }
              }
              System.out.println("线程2释放锁");
              System.out.println(ClassLayout.parseInstance(dog).toPrintable());
            },
            "t2")
        .start();
  }
}

class Dog {
  int age;
}

如上图所示,锁初始状态为101,可偏向无锁状态

当线程1在使用锁,而线程2去上锁的时候,状态已经变为010,不可偏向重量级锁。

总结

单线程使用锁的时候为偏向锁。

多线程无竞争(错峰使用锁)的时候为轻量级锁。

有竞争的时候为重量级锁。

参考

这可能是B站上最深入解析的synchronized底层原理解析_哔哩哔哩_bilibili

视频去哪了呢?_哔哩哔哩_bilibili

synchronized锁原理分析(一、从Java对象头看synchronized锁的状态)_liujianyangbj的博客-CSDN博客

Java的对象头和对象组成详解_lkforce-CSDN博客_java对象头

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-03-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java对象的内存结构
    • 对象内存结构
      • 查看对象内存结构
        • 对象头
        • 锁升级的过程
          • 前提
            • 无锁状态
              • 由无锁状态---->偏向锁状态
                • 偏向锁状态---->轻量级锁状态
                  • 重量级锁状态
                  • 总结
                  • 参考
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档