偏向锁

偏向锁

注:本文翻译自【Evaluating and improving biased locking in the HotSpot virtual machine】,译者水平有限,如词不达意,请告之!

HotSpot支持存储释放偏向锁,以及偏向锁的批量重偏向和撤销。这个特性可以通过JVM的参数进行切换,而且这是默认支持的。Unlock状态下MarkWord的一个比特位用于标识该对象偏向锁是否被使用或者是否被禁止。如果该bit位为0,则该对象未被锁定,并且禁止偏向;如果该bit位为1,则意味着该对象处于以下三种状态:
  • 匿名偏向(Anonymously biased)
    在此状态下thread_ptr为NULL(0),意味着还没有线程偏向于这个锁对象。第一个试图获取该锁的线程将会面临这个情况,使用原子CAS指令可将该锁对象绑定于当前线程。这是允许偏向锁的类对象的初始状态。
  • 可重偏向(Rebiasable)
    在此状态下,偏向锁的epoch字段是无效的(与锁对象对应的klass的mark_prototype的epoch值不匹配)。下一个试图获取锁对象的线程将会面临这个情况,使用原子CAS指令可将该锁对象绑定于当前线程。在批量重偏向的操作中,未被持有的锁对象都被至于这个状态,以便允许被快速重偏向。
  • 已偏向(Biased)
    这种状态下,thread ptr非空,且epoch为有效值——意味着其他线程正在只有这个锁对象。

基于偏向锁对象需要使用hashcode字段作为偏向线程id标识的事实,被hash的对象不可被用作偏向锁。对于允许偏向的对象在进行hashcode计算时,首先要吊销(revoke)所有的偏向(不管是有效的还是无效的),然后使用CAS将计算好的hashcode值放到MarkWord中,尽管这仅仅适用于“identity hashcode(使用Object类的hashcode()方法进行计算)”。普通Java类型hashcode的计算需要重载Object的hashcode()方法,但不必要去显示调用这个方法;因此,对于没有显示调用Object#hashcode()方法的类的对象,仍然适用于偏向锁的机制——可被用作锁对象使用。

HotSpot为所有加载的类型,在class元数据——InstanceKlass中保留了一个MarkWord原型——mark_prototype。这个值的bias位域决定了该类型的对象是否允许被偏向锁定。与此同时,当前的epoch位也被保留在prototype中。这意味着,对应class的新对象可以简单地直接拷贝这个原型值,而不必在后面进行修正。在批量重偏向(bulk rebias)的操作中,prototype的epoch位将会被更新;在批量吊销(bulk revoke)的操作中,prototype将会被置成不可偏向的状态——bias位被置0。

偏向锁的获取依靠原子CAS指令将线程指针插入MarkWord中。其先决条件是:1.该对象处于匿名偏向状态;2.该对象处于可重偏向状态(一个锁对象仅能被一个线程偏向一次)。只要锁对象被偏向,递归锁定和解锁仅仅需要读取对象头以及对应Klass的prototype去验证偏向是否被吊销。

HotSpot中偏向锁的撤销是JVM处于在全局安全点时被执行的。在撤销过程中,撤销者会遍历当前偏向线程的锁记录,藉此推断对象当前是否被锁定。如果发现锁对象被一个偏向线程持有,锁记录将被修改——如同轻量级锁被使用一样;如果锁对象未被持有,这是取决于触发撤销的原因,锁对象要么被禁止用作偏向锁,要么被禁止重新偏向于撤销线程。

即使偏向锁的特性被打开,出于性能(启动时间)的原因在JVM启动后的的头4秒钟这个feature是被禁止的。这也意味着在此期间,prototype MarkWord会将它们的bias位设置为0,以禁止实例化的对象被偏向。4秒钟之后,所有的prototype MarkWord的bias位会被重设为1,如此新的对象就可以被偏向锁定了。

获取偏向锁的步骤:

  1. 验证对象的bias位
    如果是0,则该对象不可偏向,应该使用轻量级锁算法。
  2. 验证对象所属InstanceKlass的prototype的bias位
    确认prototype的bias为是否被设置。如果没有设置,则该类所有对象全部不允许被偏向锁定;并且该类所有对象的bias位都需要被重置,使用轻量级锁替换。
  3. 校验epoch位
    校验对象的MarkWord的epoch位是否与该对象所属InstanceKlass的prototype的MarkWord的epoch匹配。如果不匹配,则表明偏向已过期,需要重新偏向。这种情况,偏向线程可以简单地使用原子CAS指令重新偏向于这个锁对象。
  4. 校验owner线程
    比较偏向线程ID与当前线程ID。如果匹配,则表明当前线程已经获得了偏向,可以安全返回。如果不匹配,对象锁被假定为匿名偏向状态,当前线程应该尝试使用CAS指令获得偏向。如果失败的话,就尝试撤销(很可能引入安全点),然后回退到轻量级锁;如果成功,当前线程成功获得偏向,可直接返回。
编辑于 2017-04-20

文章被以下专栏收录