偏向锁的 撤销(revoke)是一个很特殊的操作,为了执行撤销操作,需要等待全局安全点,此时所有的工作线程都停止了执行。偏向锁的撤销操作并不是将对象恢复到无锁可偏向的状态,而是在偏向锁的获取过程中,发现竞争并且对方并没有结束释放偏向锁时,直接将一个被偏向的对象升级到被加了轻量级锁的状态。
由于偏向锁的移除需要在全局安全点的时候执行,所以如果当有大量线程竞争同一个锁资源时,我们可以通过关闭偏向锁来调优系统性能。
https://z.itpub.net/article/detail/B87A9918BBB38652E96040B0C50A87C9
如果需要撤销的是当前线程,只要遍历当前线程的栈就能拿到lock record,可以直接调用revoke_bias,不需要等到safe point再撤销。在调用Object#hashcode时,也会走到该分支将为偏向锁的锁对象直接恢复为无锁状态。若不是当前线程,会被push到VM Thread中等到safepoint的时候再执行。
VMThread内部维护了一个VMOperationQueue类型的队列,用于保存内部提交的VM线程操作VM_operation。GC、偏向锁的撤销等操作都是在这里被执行。
撤销调用的revoke_bias方法的代码就不贴了。大致逻辑是:
步骤 1、查看偏向的线程是否存活,如果已经死亡,则直接撤销偏向锁,然后slowenter。JVM维护了一个集合存放所有存活的线程,通过遍历该集合判断某个线程是否存活。
步骤 2、偏向的线程是否还在同步块中,如果不在,则撤销偏向锁。如果在同步块中,执行步骤3。这里是否在同步块的判断基于上文提到的偏向锁的重入计数方式:在偏向锁的获取中,每次进入同步块的时候都会在栈中找到第一个可用(即栈中最高的)的Lock Record,将其obj字段指向锁对象。每次解锁的时候都会把最低的Lock Record移除掉,所以可以通过遍历线程栈中的Lock Record来判断是否还在同步块中。轻量级锁的重入也是基于Lock Record的计数来判断。
步骤 3、升级为轻量级锁。将偏向线程所有相关Lock Record的Displaced Mark Word设置为null,再将最高位的Lock Record的Displaced Mark Word 设置为无锁状态,然后将对象头指向最高位的Lock Record。这里没有用到CAS指令,因为是在safepoint,可以直接升级成轻量级锁。
从低往高遍历栈的Lock Record,将obj置为null,最高的设计displace mark word 表示轻量级锁
https://www.jianshu.com/p/4758852cbff4
======
撤销偏向锁就是将锁对象oop的对象头恢复成无锁状态或者膨胀成轻量级锁状态,执行撤销动作的前提是锁对象oop的对象头处于偏向锁状态。具体而言有以下几种情形:
执行Object类的hashcode方法,会将其恢复成无锁状态。
执行Object类的wait/notify/notifyall方法,会将其恢复成无锁状态,直接膨胀成重量级锁。
执行jni_MonitorEnter或者jni_MonitorExit方法,会将其恢复成无锁状态,直接膨胀成重量级锁。
执行Unsafe类的monitorenter/trymonitorenter/monitorexit方法,会将其恢复成无锁状态,直接膨胀成重量级锁。
尝试获取某个偏向锁,如果该偏向锁被某个线程占用了,但是没有关联的BasicObjectLock,即实际占用该偏向锁的方法已经退出了,则会将其恢复成无锁状态,然后膨胀成轻量级锁,但是在撤销一定次数后触发批量重偏向(rebasic)的情形下也可能重新获取该偏向锁。如果该偏向锁正在被某个方法所使用,即存在对应的BasicObjectLock,则直接将该偏向锁膨胀成轻量级锁。
注意偏向锁的撤销大部分情形下都是需要在安全点下执行,因为需要遍历其他线程的所有调用栈帧,判断是否存在与之关联的BasicObjectLock。在以下情形不需要在安全点下执行:
目标对象的对象头是匿名偏向锁状态
目标对象的Klass的prototype_header变成无锁状态
目标对象的Klass的prototype_header中的epoch值和目标对象对象头中的epoch值不一样
目标对象的偏向锁由当前线程持有
https://blog.csdn.net/qq_31865983/article/details/105024397