JVM 内置锁 synchronized 的几种状态概述(上)

简介: JVM 内置锁 synchronized 的几种状态概述

内置锁使用


通常我们说的 java 内置锁默认都是指的 JVM 给我们提供的 synchronized 关键字实现的锁。 下面是一个简单的例子:


public class SynchronizedVariableTest1 {
    public static void main(String[] args) throws InterruptedException {
        SynchronizedVariableTest1 test = new SynchronizedVariableTest1();
        synchronized (test) {
            System.out.println(1);
        }
    }
}


对象加锁


我们可以通过 javap命令查看字节码文件,或者通过 idea 的jclasslib Bytecode Viewer插件进行查看字节码指令信息,如下图所示:


image.png


我们看到 synchronized 底层是使用  monitor 机制来实现锁的获取、释放。会在代码快前后增加 monitorentermonitorexit 指令。


**锁定对象不能是 null , 如果是 null 程序运行的时候会提示 ****NullPointerException**空指针异常。


Object lock = null;
synchronized(lock) {
    System.out.println(100);
}
// 结果: 
// Exception in thread "main" java.lang.NullPointerException
//  at cn.xyz.juc.synchronized1.status.CleanLockTest.main(CleanLockTest.java:16)


方法加锁


同样,如果是在方法上增加 synchronized关键字(由于jclasslib Bytecode Viewer查看方法信息不是很方便,下面我就通过 javap -verbose指令来进行演示),会在方法的 flags 上增加 ACC_SYNCHRONIZED关键字,原方法代码如下:


public synchronized void test() {
}


编译后的字节码, 执行指令 javap -verbose xxxx.class


public synchronized void test();
    descriptor: ()V
    flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: iconst_1
         4: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
        line 14: 21
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      22     0  this   Lcn/xyz/juc/synchronized1/SynchronizedVariableTest2;


对于 .class字节码文件的解析和分析可以参考这篇掘金文章: JVM 字节码指令解析


内置锁状态存储


内置锁的状态是存储到 Java 对象的对象头中,对象头的存储结构,以及对象内存分配可以参考我的这篇文章: 基于 Hostpot 虚拟机的 Java 对象揭秘 。本文不再赘述。


Mark Word (64bit)


为了方便下文阅读,我把mark word 再贴一次到本文中


image.png


Java 管程


Java 虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是采用管程(Monitor, 更常见的是直接称为 “锁”)来实现的。


Java 采用的是管程技术,synchronized 关键字及 wait()、notify()、notifyAll() 这三个方法都是管程的组成部分。而管程和信号量是等价的,所谓等价指的是用管程能够实现信号量,也能用信号量实现管程。但是管程利用OOP的封装特性解决了信号量在工程实践上的复杂性问题,因此java采用管理机制。


MESA 模型


管程模型,分别是:Hasen 模型、Hoare 模型和 MESA 模型。其中,现在广泛应用的是 MESA 模型,并且 Java 管程的实现参考的也是 MESA 模型。


在并发编程领域,有两大核心问题:一个是互斥,即同一时刻只允许一个线程访问共享资源;另一个是同步,即线程之间如何通信、协作。这两大问题,管程都是能够解决的。


ObjectMonitor


ObjectMonitor 在 jvm 中的定义信息如下:


// initialize the monitor, exception the semaphore, all other fields
  // are simple integers or pointers
  ObjectMonitor() {
    _header       = NULL;      // 对象头
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;         // 锁重入次数   
    _object       = NULL;      // 存储锁对象
    _owner        = NULL;      // 标识拥有该 monitor 的线程(当前获取锁的线程) 
    _WaitSet      = NULL;      // 等待线程(调用 waite) 组成的双向循环链表 _WaitSet 是第一个节点
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;      
    _succ         = NULL ;
    _cxq          = NULL ;     // 多线程竞争锁会先存储到这个单向链表中(FIFO)结构
    FreeNext      = NULL ;     // 存放在进入或者重新进入时被阻塞(Blocked)的线程(也就是竞争失败的线程)
    _EntryList    = NULL ;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }


内置锁状态


内置锁分为 4 个状态,分别是:无锁,偏向锁,轻量级锁,重量级锁。在锁竞争的过程中会进行一个正向的锁升级过程。


锁升级过程


image.png


说明,锁升级状态是不可逆转的。


无锁状态


实验代码:


public class NoSynchronizedTest {
    public static void main(String[] args) {
        NoSynchronizedTest test = new NoSynchronizedTest();
        System.out.println("无锁状态 +++++++++");
        System.out.println(ClassLayout.parseInstance(test).toPrintable());
    }
}


输出结果


无锁状态 +++++++++
cn.xyz.juc.synchronized1.NoSynchronizedTest object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           05 c0 00 f8 (00000101 11000000 00000000 11111000) (-134168571)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


字段解释:


  • OFFSET : 地址偏移量,单位字节;


  • SIZE: 占用内存大小,单位字节;


  • TYPE DESCRIPTION: 类型描述,其中 object header 为对象头;


  • VALUE: 对应内存中当前存储的值,二进制 32 ;


指针压缩


打印的结果我们可以看到,对象总大小位 16 字节,前 12 字节为对象头(我本地 jdk 1.8 默认开启指针压缩),后面 4 字节为对齐填充。


可以通过一下参数进行关闭:


-XX:-UseCompressedOops


我再执行一次,对象总大小 16 bytes, 前 8 bytes 是 mark word , 后 8 bytes 表示 kclass point


image.png


匿名偏向

当 JVM 启用了偏向锁模式(JDK 6默认开启),创建新的 Mark Word 的 Thread Id 为 0, 说明此时处于可偏向但是并未偏向任何线程,也叫做匿名偏向状态(anonymously biased)


偏向锁状态


偏向锁延迟偏向


**偏向锁模式存在偏向锁延迟机制: **Hostpost 虚拟机再启动后有一个几秒(默认 4 秒)的延迟才对妈给新对象进行开启偏向锁模式。 JVM 启动时会进行一系列的对象创建过程。在这个过程中大量 synchronized 关键字对对象加锁,这些锁多数都不是偏向锁。为了减少初始化时间, jvm 默认延迟加载偏向锁。


相关文章
|
6月前
|
存储 监控 安全
Java虚拟机的锁优化策略
Java虚拟机的锁优化策略
59 0
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4
|
4月前
|
缓存 安全 算法
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
49 0
|
1月前
|
存储 安全 Java
JVM锁的膨胀过程与锁内存变化解析
在Java虚拟机(JVM)中,锁机制是确保多线程环境下数据一致性和线程安全的重要手段。随着线程对共享资源的竞争程度不同,JVM中的锁会经历从低级到高级的膨胀过程,以适应不同的并发场景。本文将深入探讨JVM锁的膨胀过程,以及锁在内存中的变化。
41 1
|
1月前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。
|
6月前
|
Java 应用服务中间件
深入理解JVM - 类加载器概述
深入理解JVM - 类加载器概述
56 0
|
6月前
|
消息中间件 算法 Java
三面“有赞”Java岗斩获offer:Spring+JVM+并发锁+分布式+算法
年末离职,年初为面试也筹备挺长一段时间,找了不少复习资料,刷了很多题在网上投了很多简历最终面试了有赞,还有幸拿到offer!
|
安全 前端开发 Java
JVM概述和类加载子系统
我记得当年学java的时候,就很好奇,为什么我在IDEA上写一些代码(其实就是一堆我们人能知道的英文单词的组合加一些运算符),为什么就可以在windows上运行后执行我们的指令,而且还可以打成jar包去linux系统跑起来,为什么一份代码可以在不同平台运行呢?类是如何加载的?对象如何创建的以及都有哪些信息?我创建的对象被分配到哪个内存去了?java是怎么和我们操作系统打交道的又是怎么调用CPU为我们计算的?创建了对象分配了内存,为什么可以不用手动回收就可以自动清理内存等等等,相信你也同样有过这些困惑。
81 0
|
缓存 Java 大数据
深入解析JVM调优:解决OutOfMemoryError、内存泄露、线程死锁、锁争用和高CPU消耗问题
深入解析JVM调优:解决OutOfMemoryError、内存泄露、线程死锁、锁争用和高CPU消耗问题
223 0
|
安全 前端开发 Java
JVM(一)JVM概述
JVM(一)JVM概述
62 0