对象在堆内存中的布局:
markword数据结构
锁升级过程:
流程图:
无锁 --> 轻量级锁过程:
对象创建后,偏向锁启动延迟为4s,在4s之前所有的对象markword的后三位为001(无锁)此时加锁会直接升级为00(轻量级锁),延迟后后三位变为101,(匿名偏向),此时加锁会升级为偏向锁,偏向锁的54bit指向该加锁线程。
延迟4s后情况:
可以看到加上了偏向锁(biased),在释放锁后,markword还是110已经偏向状态
未延迟:
可以看到直接加上了轻量级锁(thin lock),在释放锁后,markword中的数据变为01无锁
关于重偏向
BiasedLockingBulkRebiasThreshold 默认20,偏向锁开启重偏向阈值
BiasedLockingBulkRevokeThreshold 默认40,偏向锁撤销阈值,如果超过40禁用偏向锁
BiasedLockingDecayTime,默认25s,当超过20后,25s累计的总数超过40,如果未超过重置计数为20
创建了40个对象,并给每个对象进行加锁,后面开启一个线程对前20个进行加锁,此时前19个会将偏向锁撤销,之后升级为轻量级锁。但是第20个会进行重偏向,直接偏向新的线程
代码:
效果:第18时重偏向阈值为19,第20时触发重偏向
代码:开启另一线程,再次去触发偏向锁撤销,由于前面新建的线程无法进行偏向锁撤销了,只是进行重偏向,修改markword中的线程指向。
效果:可以看到新的线程再次触发偏向锁撤销,此时,全部变成了轻量级锁
轻量级 --> 重量级:
加锁过程:
在无锁状态(010),jvm会在当前线程栈帧中创建lock record,其中包括displaced mark word 以及 owner,之后复制 mark word 到 displaced mark word中,之后该线程堵塞。JVM CAS修改mw中的lock record指向当前线程,并将owner指向mw,如果成功,加锁成功。失败的话会尝试自旋,如果超过一定次数则会将mw改成重量级锁。
释放锁:
尝试将lr中的displaced mark word 替换 mw,如果替换成功,则解锁成功,替换失败的话有可能是因为mw以及被修改为重量级锁了,直接当前锁,并唤醒其他线程。
总体流程:
关于重量级锁
Monitor:
每个对象都关联着一个monitor,主要包括entrylist, owner,waitset
当为重量级锁中,mw中存放着monitor对象的地址,线程首先尝试获取monitor对象,如果owner不为null则进入entryset进行堵塞,为null的话就执行同步块,期间如执行wait方法会进入到waitset中,等待notify或者notifyall,再次进入将owner指向自己。
参考:
https://zhuanlan.zhihu.com/p/364080848
总结:
JVM锁升级的过程:
首先创建锁对象时,该锁对象有可能处在无锁态,匿名偏向锁态主要看是否开启了偏向锁延迟。
当为匿名偏向锁态时,可以加上偏向锁,修改mw中的线程指针指向当前线程,下次该线程再次获取锁只需要判断mw是否相同,相同直接进入,结束后mw还是偏向状态,否则撤销偏向锁升级为轻量级锁,当可偏向达到阈值时会启动重偏向,直接将mw修改为当前线程。当再次超过撤销的阈值40时会将该锁对象置位01状态,无锁态此后只会升级为轻量级锁。
当为无锁态时,只能升级为轻量级锁,升级为轻量级锁会在线程的栈帧中创建lock record,并将mw中复制到displaced mark word中,之后会进行cas将mw中的lock record替换为栈帧中的lr地址,如果成功的话就加锁成功,释放锁的步骤是将dmw中还原到mw中,此时mw又为无锁态了。如果cas失败的话会进行自旋操作,超过一定次数的话会修改mw为重量级锁。