目录

一 对象在内存中的布局? 创建一个Object对象占用多少内存?

1.1 对象在内存中的布局?

1.2 创建一个Object对象占用多少内存?

二Safepoint(安全点)?

2.1 什么是safepoint (安全点)

2.2 常见的safepoint场景

2.3 safepoint插入的位置

三 锁的分类

3.1 线程是不是需要锁住同步资源

3.1.2 悲观锁

3.1.2 乐观锁

3.2 多个线程竞争锁的是否需要排队

3.2.1 公平锁

3.2.2 非公平锁

3.3. 一个线程是否可以多次获取同一个锁

3.3.1 可重入锁

3.3.2 不可重入锁

3.4 多个线程能不能共享一把锁

3.4.1 共享锁

3.4.2 排它锁、互斥锁

3.5 根据获取锁失败后是否阻塞分类

3.5.1 自旋锁

3.6 根据线程数量和竞争状态激烈程度分类

3.6.1 重量级锁

3.6.2 轻量级锁

3.6.3 偏向锁

四 什么是Lock Record?

五 什么是监视器或者Monitor

5.1 监视器

5.2 工作流程

5.2.1 线程竞争锁

5.2.2 线程锁竞争成功

5.2.3 线程竞争锁失败

5.2.4 运行中的线程调用wait方法后,会被挂起

5.2.5 阻塞队列中线程时间到期或者被唤醒

5.2.6 轻量级锁膨胀之后,会导致竞争的线程加入到竞争失败队列_cxq中,然后挂起线程


深入分析偏向锁、轻量级锁和重量级锁

一 对象在内存中的布局? 创建一个Object对象占用多少内存?

1.1 对象在内存中的布局?

我们知道,对象是放在堆中的,那么对象是怎么构成的?只包括实例数据吗?堆中的对象主要由三部分组成:对象头(object header)、实例数据(instance data)和对齐填充(padding)。

对象头:主要由三部分组成,包括mark word(关于hashcode、锁或者GC分代信息的记录), class pointer(指向方法区class的指针)、数组长度。

其中mark word的长度,根据是32位操作系统还是64位操作系统不同而不同,如果是32位操作系统,那么长度是32位;如果是64位操作系统,那么长度是64位。以下几点需需要我们注意:

第一:对象刚刚创建的时候,没有线程竞争它,也就是没有遇到同步代码块或者同步方法等。此时处于无锁状态,但是并不会立即生成hashcode数据,而是被调用hashcode方法的时候才会生成。

第二:在偏向锁加锁的时候,如果该线程调用了hashcode,那么此时因为偏向锁没有保存hashcode,所以偏向锁会升级为轻量级锁。

第三:轻量级锁会把锁对象的mark word拷贝到自己线程栈帧中的lock record,在轻量级锁释放的时候displaced mark word又被替换回mark word。所以轻量级锁hashcode、分代信息等都是保存在持有锁的线程的栈帧中的displaced mark word中

第四:对于重量级锁,或者轻量级锁释放失败需要升级为重量级锁的时候,hashcode、分代信息是保存在监视器ObjectMonitor中的_header字段中

mark word结构如图所示:

实例数据:就是实例中根的字段和数据,如果没有数据,那么实例的长度为0,比如Object对象。

对齐填充:因为Java对象必须是8字节的倍数,如果不是8的倍数则对齐填充

1.2 创建一个Object对象占用多少内存?

原理: object header size + instance data size + padding

object header size = mark word size(8) + class pointer(4) + array size(如果有数组才有这个,object这里为0) = 12

instance data size = 0 (object没有字段)

padding = 4 (java对象必须是8的倍数,现在12不是8的倍数,所以需要对齐填充,比12大的最小8的倍数就是16,所以需要4字节对齐填充)

所以:创建一个Object对象占用多少内存总共需要16字节内存

二Safepoint(安全点)?

在JVM中,有时候需要执行一些全局性的操作,但是并不是在字节码执行的任何时候都适合去做这些操作,如果这样有可能导致异常。比如回收垃圾,需要暂停所有应用线程,但是只有等所有的应用线程暂停才可以开始。

2.1 什么是safepoint (安全点)

safepoint就是一个安全点,所有的线程执行到安全点的时候就会去检查是否需要执行safepoint操作,如果需要执行,那么所有的线程都将会等待,直到所有的线程进入safepoint。然后在JVM执行之后,所有的线程再恢复执行。

2.2 常见的safepoint场景

第一:GC回收

第二:偏向锁撤销

2.3 safepoint插入的位置

JVM会在字节码执行的某些时刻才会去做这些事情,常见的Safepoint位置有循环的结尾、方法返回之前、抛出异常的位置等。之所以选择这些位置作为safepoint的插入点,主要的考虑是避免程序长时间运行而不进入safepoint。比如GC的时候必须要等到Java线程都进入到safepoint的时候VMThread才能开始执行GC,如果程序长时间运行而没有进入safepoint,那么GC也无法开始,JVM可能进入到Freezen假死状态。

三 锁的分类

3.1 线程是不是需要锁住同步资源

3.1.2 悲观锁

认为每次都有其他线程修改数据,可能带来并发问题,所以在读写数据的时候都会对资源加锁,其他线程只能等待当前线程释放锁之后再抢占锁。常见的实现比如数据库Java中synchronized关键字或者MySQL排它锁。

场景:适用于写多的场景。因为使用乐观锁会不停的自旋,如果写线程多的话会消耗大量的CPU资源,从而影响性能。

3.1.2 乐观锁

不会或者只有很少的线程可能会修改数据,为资源提供提供一个版本号,每次比较版本更新前后是否一致,如果一致,则修改成功;否则重新获取新版本数据,重新进行修改,直到修改成功。常见的实现就是CAS。

场景:适用于读多写少的场景。因为写少,所以并发量就少,那么即使不停的自旋,问题也不大,而且也避免了加锁这种重量级的操作。

3.2 多个线程竞争锁的是否需要排队

3.2.1 公平锁

线程进入队列排队,根据先进先出的顺序获取锁

3.2.2 非公平锁

没有排队的锁

3.3. 一个线程是否可以多次获取同一个锁

3.3.1 可重入锁

可以多次获取同一个锁

3.3.2 不可重入锁

不能多次获取同一个锁

3.4 多个线程能不能共享一把锁

3.4.1 共享锁

多个线程可以共享一把锁

3.4.2 排它锁、互斥锁

线程之间不能共享一把锁

3.5 根据获取锁失败后是否阻塞分类

3.5.1 自旋锁

如果线程获取锁之后,还可以不停的重试,进行自旋,直到成功或者超过自旋次数。

3.6 根据线程数量和竞争状态激烈程度分类

3.6.1 重量级锁

执行同步代码块的时候或者执行同步方法的时候,需要为锁对象创建监视器,监视器用于关联锁对象、以及锁对象的原始头信息、重入次数、竞争失败队列、竞争队列和阻塞队列等信息,所以保存的东西占用的内存量很多,尤其是线程并发量大的时候;另外线程竞争失败的队列需要进入队列挂起或者线程阻塞也需要挂起,当挂起的时间到期又需要唤醒线程,等待和唤醒是属于系统调用,会涉及到CPU在用户态和内核态切换,线程太多就会频繁切换。

3.6.2 轻量级锁

当只有少量线程的时候,并发程度很低,不存在同时竞争锁的情况,只需要尝试将自己的线程栈帧中的锁记录信息设置到锁对象中就可以,不需要进行等待和唤醒等操作,所以不存在内核态和用户态的切换,只有系统调用才会发生内核态和用户态的切换;而且还少了操作系统调度,提升了性能。所以它是轻量级的。

3.6.3 偏向锁

当经常只有一个线程执行同步代码块的时候,其实复制mark word到锁记录displaced mark word都是没有必要的, 只要在锁对象中设置一个线程标识,每一次校验线程ID是不是持有锁的线程ID, 如果持有则可以执行同步代码块,没有多余的其他操作。这个偏向于某一线程的锁,就叫做偏向锁。

四 什么是Lock Record?

当锁对象升级到轻量级锁的时候,线程运行的时候在自己栈帧中分配的一块空间,用于存储锁对象头中mark word原始信息,拷贝过来的mark word也叫作displaced mark word,这块空间就是Lock Record,用于保存锁记录信息。

为什么要将锁对象的mark word拷贝到持有轻量级锁的线程的栈帧中?这样获取锁之后,还能保存锁对象的原始的hashcode、分代信息等。这也是轻量级锁和偏向锁的区别之一,否则如果只是和偏向锁类似,将锁对象通过一个线程ID标识当前被哪一个线程持有,那么在释放锁的时候只需要将这个线程ID清空就可以。但是如何保存锁对象的原始信息,比如hashcode和分代信息等,那就只有升级为重量级锁了,由监视器去保存这些东西。在代码实现锁记录主要由BasicObjectLock和BasicLock实现,一个BasicLock表示保存锁记录;BasicObjectLock关联锁对象和BasicLock。

class BasicObjectLock VALUE_OBJ_CLASS_SPEC {friend class VMStructs;private:BasicLock _lock; // 锁记录oop       _obj;  // 关联的锁对象              }class BasicLock VALUE_OBJ_CLASS_SPEC {friend class VMStructs;private:volatile markOop _displaced_header; // 保存锁对象的mark word}

五 什么是监视器或者Monitor

5.1 监视器

Monitor,又叫做ObjectMonitor或者监视器或者管程,主要用来监视锁对象。当轻量级锁升级到重量级锁的时候,此时JVM会创建一个管程或者说监视器来控制对共享资源的访问,保证多个线程访问同一个资源的时候可以达到互斥和同步的效果。

监视器主要包含以下字段信息:

ObjectMonitor() {_header       = NULL; // 锁对象mark word信息,保存hashcode或者分代信息_count        = 0; // 记录线程获取锁的次数_waiters      = 0, // 等待获取锁的线程_recursions   = 0; // 记录锁的重入次数_object       = NULL; // 锁对象_owner        = NULL;  // 指向持有这个monitor对象(锁)的线程_WaitSet      = NULL;  // 存放处于等待状态的线程_WaitSetLock  = 0 ;_Responsible  = NULL ;_succ         = NULL ; // 假定继承人,被唤醒的线程,准备获取锁_cxq          = NULL ; // 竞争失败队列FreeNext      = NULL ;_EntryList    = NULL ; // 存放竞争锁的线程队列_SpinFreq     = 0 ; // 自旋频率_SpinClock    = 0 ;OwnerIsThread = 0 ; // 锁的持有者是否是线程,如果是轻量级锁,持有者最开始可能是lock record,并不是线程_previous_owner_tid = 0;}

5.2 工作流程

5.2.1 线程竞争锁

新到来的线程和释放锁唤醒的线程(假定继承人)会同时通过CAS方式竞争锁,即将监视器中的线程持有者_owner置为自己

5.2.2 线程锁竞争成功

线程竞争锁成功,就可以进入临界区执行代码

5.2.3 线程竞争锁失败

根据不同的唤醒策略QMode,那么失败后的处理结果不一样:

情况一:QMode= 2

失败的线程封装成ObjectWaiter,然后放入到_cxq锁竞争失败队列,并且唤醒队首的线程(此时唤醒的线程就是假定继承者),然后返回。

情况二:QMode = 3

失败的线程封装成ObjectWaiter,然后放入到_cxq锁竞争失败队列,并且将_cxq队列中所有元素插入到_EntryList队尾,并且唤醒_EntryList队首线程(此时唤醒的线程就是假定继承者)。

情况三:QMode = 4

失败的线程封装成ObjectWaiter,然后放入到_cxq锁竞争失败队列,并且将_cxq队列中所有元素插入到_EntryList队首,并且唤醒_EntryList队首线程(此时唤醒的线程就是假定继承者)。

5.2.4 运行中的线程调用wait方法后,会被挂起

运行中的线程调用wait方法后,会被挂起,放入到阻塞队列中_WaitSet中

5.2.5 阻塞队列中线程时间到期或者被唤醒

阻塞队列_WaitSet中线程时间到期或者被唤醒(notify),不会立刻抢占锁,而是先被放入到_EntryList中或者_cxq队列中(取决于Knob_MoveNotifyee这个值)

5.2.6 轻量级锁膨胀之后,会导致竞争的线程加入到竞争失败队列_cxq中,然后挂起线程

2021-06-01 深入分析锁升级流程的基础相关推荐

  1. synchonized只能回答加锁?深入解析,关于锁升级流程的各项细节

    目录 锁升级 线程获取锁流程 轻量级锁升级为重量级锁 偏向锁 策略 案例 最后 关于synchronized,我相信你一定不会陌生,但是在java 6之后,jdk大幅修改了锁的量级,从原来的重量级锁变 ...

  2. 499、Java分布式和集群12 -【SpringCloud视图微服务 - 消息总线Bus】 2021.06.01

    目录 0.RabbitMQ 1.先运行,看到效果,再学习 2.pom.xml 3.bootstrap.yml 4.application.yml 5.ProductDataServiceApplica ...

  3. 《惢客创业日记》2021.06.01(周二)五月份的工作总结

    时间真快,五月一晃而过,感觉时间就像一辆没有停靠站台的疾驰列车,装载着每个人的经历和人生驶向没有尽头的远方.不管是对,还是错,是得到,还是失去,都将其存储到每个人的大脑记忆中,由此让我想到了一句话:& ...

  4. JVM内部锁升级过程(偏向锁,轻量级锁,重量级锁)

    目录 对象在内存中是如何布局的 如何查看对象在内存中的布局 markword数据结构 加锁后发生了什么 偏向锁 什么是偏向锁 偏向锁定时hashCode 哪去了? 为什么需要偏向锁 为什么从JDK15 ...

  5. java 偏向锁_Java并发之彻底搞懂偏向锁升级为轻量级锁

    网上有许多讲偏向锁,轻量级锁的文章,但对偏向锁如何升级讲的不够明白,有些文章还相互矛盾,经过对jvm源码(biasedLocking.cpp)的仔细分析和追踪,基本升级过程有了一个清晰的过程,现将升级 ...

  6. 第十二章:synchronized与锁升级

    相关面试题 锁优化背景 Synchronized 锁性能变化 jdk5 以前 复习:为什么任意一个对象都能成为锁? jdk6 之后 synchronized的种类以及锁升级流程 锁升级流程 无锁 偏向 ...

  7. 锁升级过程(无锁、偏向锁、轻量级锁、重量级锁)

    文章目录 Synchronized锁升级的背景 Synchronized的性能变化 Java5之前,用户态和内核态之间的切换 为什么每个对象都可以称为一把锁? Java6开始优化Synchronize ...

  8. Mculover666的博客文章导航(嵌入式宝藏站)(2021.06.17更新)

    一.MCU系列 1. 开发环境 [Keil MDK](一)Keil MDK 5.28 的下载.安装.破解 [Keil MDK](二)Keil MDK中芯片器件包的安装 [Keil MDK](三)Kei ...

  9. synchronized锁升级过程详解

    32位: 64位: 无锁: 1001001110000101111101010101110 HashCode:1237514926 十进制:1237514926 二进制:0100100 1100001 ...

最新文章

  1. 7.0、Android Studio命令行工具
  2. mysql仅php_php – MySQL仅更新表中的某些字段
  3. spring boot地一讲
  4. mvc ajax提交多选,javascript – 如何使用Jquery AJAX调用MVC Action然后在MVC中提交表单?...
  5. 往ABAP gateway system上和Cloud Foundry上部署HTML5应用
  6. P2742 [USACO5.1]圈奶牛Fencing the Cows /【模板】二维凸包
  7. spring依赖日志_Spring:设置日志记录依赖项
  8. python中产生随机数模块_Python中random模块生成随机数详解
  9. python猴子偷桃_Python实例100个(基于最新Python3.7版本)
  10. 使用mysqladmin命令修改MySQL密码与忘记密码
  11. Exchange企业实战技巧(18)日志规则应用之审计邮箱
  12. JavaScript中this指针的绑定规则
  13. 先有产品管理,后有产品经理
  14. Python与Julia : parquet、feather格式比较
  15. Springboot 内嵌 Tomcat 版本查看
  16. 查看服务器ip配置信息,怎么查看服务器ip地址,怎么查看ip地址和端口
  17. Mac常见问题:无线键盘失灵!
  18. 360漏洞修复的提取
  19. Codeforces 1342E Placing Rooks(容斥+组合数学)
  20. python标准图形库——turtle

热门文章

  1. html 右上角删除图标,网页制作html+css制作div标签增加右上角删除图标的示例代码...
  2. oracle flash_cache,11gR2新特性之二 - Flash Cache 的SSD支持
  3. redistemplate 设置失效时间_开发新技能之利用Redis高级用法监听过期键处理失效的订单...
  4. 获取屏幕的宽高 android,Android获取屏幕宽高的方法
  5. linux如何运行sh监控文件夹,如何使用Shell进行文件监控?
  6. Zookeeper集群详解
  7. java 滚动显示信息_滚动显示文本的Java程序
  8. mysql注入原理_Mysql报错注入原理分析
  9. java list find方法_Java 8 Stream - Find Max and Min from List - 入门小站-rumenz.com
  10. python从url获取pdf文件并保存在本地