java对象头由3部分组成:

1、Mark Word

2、指向类对象(对象的class对象)的指针

3、数组长度(数组类型才有)

重点是 Mark Word结构,下面以32位HotSpot为例:

一、偏向锁

1、概念:

HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低从而引入偏向锁。偏向锁在获取资源的时候会在锁对象头上记录当前线程ID,偏向锁并不会主动释放,这样每次偏向锁进入的时候都会判断锁对象头中线程ID是否为自己,如果是则不需要进行额外的操作,直接进入同步操作。

2、偏向锁的获取过程:

I:判断是否为可偏向状态--MarkWord中锁标志是否为‘01’,是否偏向锁是否为‘1’

II:如果是可偏向状态,则查看线程ID是否为当前线程,如果是,则进入步骤'V',否则进入步骤‘III’

III:通过CAS操作竞争锁,如果竞争成功,则将MarkWord中线程ID设置为当前线程ID,然后执行‘V’;竞争失败,则执行‘IV’

IV:CAS获取偏向锁失败表示有竞争。当达到safepoint时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码块

V:执行同步代码

3、偏向锁的撤销过程:

I:偏向锁不会主动释放(撤销),只有遇到其他线程竞争时才会执行撤销,由于撤销需要知道当前持有该偏向锁的线程栈状态,因此要等到safepoint时执行,此时持有该偏向锁的线程(T)有‘II’,‘III’两种情况;

II:撤销----T线程已经退出同步代码块,或者已经不再存活,则直接撤销偏向锁,变成无锁状态----该状态达到阈值20则执行批量重偏向

III:升级----T线程还在同步代码块中,则将T线程的偏向锁升级为轻量级锁,当前线程执行轻量级锁状态下的锁获取步骤----该状态达到阈值40则执行批量撤销

4、批量重偏向/撤销:

从‘3、偏向锁的撤销过程’可以看出偏向锁需要等到safepoint才能进行锁升级/撤销,这种情况偏向锁不仅不能提高性能,反而会导致性能下降,思考两个场景:

I:线程T1创建了大量对象,并进行初始的同步操作,这时这些对象都偏向T1;之后线程T2使用这些对象作为锁进行同步操作,则会存在大量的偏向锁撤销操作

II:存在明显多线程锁竞争时会存在大量偏向锁升级过程,为了解决这两个问题,jvm提供了批量重偏向/撤销的机制

批量重偏向/撤销过程:

I:首先引入一个概念epoch,其本质是一个时间戳,代表了偏向锁的有效性,epoch存储在可偏向对象的MarkWord中。除了对象中的epoch,对象所属的类class信息中,也会保存一个epoch值

II:每当遇到一个全局安全点时(这里的意思是说批量重偏向没有完全替代了全局安全点,全局安全点是一直存在的),比如要对class C 进行批量再偏向,则首先对 class C中保存的epoch进行增加操作,得到一个新的epoch_new

III:然后扫描所有持有 class C 实例的线程栈,根据线程栈的信息判断出该线程是否锁定了该对象,仅将epoch_new的值赋给被锁定的对象中,也就是现在偏向锁还在被使用的对象才会被赋值epoch_new

IV:退出安全点后,当有线程需要尝试获取偏向锁时,直接检查 class C 中存储的 epoch 值是否与目标对象中存储的 epoch 值相等, 如果不相等,则说明该对象的偏向锁已经无效了(即用完了,因为'III'步骤里面已经说了只有偏向锁还在被使用的对象才会有epoch_new,这里不相等的原因是class C里面的epoch值是epoch_new,而当前对象的epoch里面的值还是epoch),此时竞争线程可以尝试对此对象重新进行偏向操作,即通过CAS操作将其Mark Word的Thread Id 改成当前线程Id

V:当epoch达到重偏向阈值(默认20)时,jvm就认为该class的偏向锁偏向的线程有问题,因此会进行批量重偏向。当epoch达到批量撤销阈值(默认40)时,jvm就认为这个class不再适合偏向锁,就会批量撤销,并且在之后的加锁过程中直接为该class的对象设置轻量级锁

二、轻量级锁

1、概念:

轻量级锁是相对于重量级锁需要阻塞/唤醒涉及上下文切换而言,主要针对多个线程在不同时间请求同一把锁的场景。

2、轻量级锁获取过程:

I:进行加锁操作时,jvm会判断是否已经时重量级锁,如果不是,则会在当前线程栈帧中划出一块空间,作为该锁的锁记录,并且将锁对象MarkWord复制到该锁记录中

II:复制成功之后,jvm使用CAS操作将对象头MarkWord更新为指向锁记录的指针,并将锁记录里的owner指针指向对象头的MarkWord。如果成功,则执行‘III’,否则执行‘IV’

III:更新成功,则当前线程持有该对象锁,并且对象MarkWord锁标志设置为‘00’,即表示此对象处于轻量级锁状态

IV:更新失败,jvm先检查对象MarkWord是否指向当前线程栈帧中的锁记录,如果是则执行‘V’,否则执行‘VI’

V:表示锁重入;然后当前线程栈帧中增加一个锁记录第一部分(Displaced Mark Word)为null,并指向Mark Word的锁对象,起到一个重入计数器的作用。

VI:表示该锁对象已经被其他线程抢占,则进行自旋等待(默认10次),等待次数达到阈值仍未获取到锁,则升级为重量级锁

3、轻量级锁解锁过程:

I:通过CAS操作把尝试把线程栈帧中复制的锁记录中的Displaced Mark Word替换当前对象头的MarkWord(即还原对象头)

II:替换成功则同步块执行顺利结束

III:替换失败说明已经膨胀为重量级锁,则在执行完同步块释放锁同时唤醒被挂起的线程

4、自适应自旋:

根据以往自旋等待时是否能够获得锁,来动态调整自旋的时间(循环数目)

三、重量级锁

1、重量级锁加锁过程:

I:调用omAlloc分配一个ObjectMonitor对象,把Mark Word锁标志置为‘10’,然后Mark Word存储指向ObjectMonitor对象的指针。ObjectMonitor对象有两个队列和一个指针,每个需要获取锁的线程都包装成ObjectWaiter对象

II:多个线程同时执行同一段同步代码时,ObjectWaiter先进入_EntryList队列,当某个线程获取到对象的monitor以后进入_Owner区域,并把monitor中的owner变量设置为当前线程同时monitor中的计数器count+1;

2、重量级锁释放过程:

I:若同步块中的线程调用wait()方法,则释放持有的monitor,owner遍历置为null,count-1,同时线程进入_WaitSet等待被唤醒

II:若当前同步块执行完毕,则也释放持有的monitor,owner遍历置为null,count-1

最新文章

  1. 第一次用golang写个小程序
  2. Android通过webservice连接SQLServer 详细教程(数据库+服务器+客户端)
  3. WebLogic 12c控制台上传获取webshell
  4. html的层叠次序---真没有想象的简单
  5. Kosaraju 算法
  6. JDBC 连接池
  7. win7里开始菜单属性里的隐私项无法选择解决方法
  8. Css 单图片按钮实例(css 图片变换)
  9. http://www.jobui.com/mianshiti/it/java/6782/
  10. Intellij idea 导入 jdbc
  11. [SinGuLaRiTy] Nescafe 24杯模拟赛
  12. C语言货架02
  13. 8分钟学会Consul集群搭建及微服务概念
  14. HBase原理分析
  15. mysql查询两个日期之前相隔的天数
  16. jquery 获取name一样的值
  17. adb logcat查看某个进程的输出日志
  18. 石子合并(区间DP经典例题)
  19. DevOps需要的工具
  20. 对json数据key进行替换

热门文章

  1. 2020 NUPCTF pwn题目
  2. [BUUCTF]PWN——bjdctf_2020_babyrop2
  3. signal 信号
  4. TURN协议(RFC5766详解)
  5. 重学c#系列——string.empty 和 "" 还有null[二十]
  6. 使用xlsx实现Excel导入
  7. React Color使用
  8. 使用iframe实现上下窗口结构及登录页全窗口展示Demo
  9. 谷歌浏览器请求返回JSON内容自动格式化
  10. This request has been blocked; the content must be served over HTTPS.处理方案