这篇文章是上篇文章是否真的理解了偏向锁、轻量级锁、重量级锁(锁膨胀)、自旋锁、锁消除、锁粗化,知道重偏向吗?的补充,对于偏向锁,网上有些对于它的原理解读过于简单,简单得似乎是错误的,最显眼的是对于Mark Word的倒数第三位的作用的含义,许多博客对于这个的作用搞成标志是否使用偏向锁,其实还有层含义是是否禁用偏向。
这篇文章提出了对于一书“深入理解Java虚拟机”中的一张图的深入是否使用偏向锁,解读,当然这张图是Oracle官方的,呵呵。还必须具有深入知道偏向锁、轻量级锁、重量级锁(锁膨胀)、自旋锁、锁消除、锁粗化这几个的过程,如果不知道。前面那个传送门可以去看一下,我是从操作系统层的PV操作的角度入手解读轻量级锁和偏向锁的,当然JVM底层实现调用操作系统的api的确是用的那个,针对于Monitor,C没有实现Monitor,Java却实现了,提供给我们很好的Monitor实现工具。像synchronized和ReetrantLock,不过这儿声明,synchronized单个不是Monitor,这儿给个我在一本书上看到Monitor(管程)的定义“管程由一些共享数据、一组能为并发进程所执行的作用在共享数据上的操作的集合、初始代码以及存取权组成。”,从这个上面解读,Monitor还包括很多东西,还和业务数据有关。不过在网上看的很多对于Java中Monitor的解读似乎很不符合这个原则,我也不知道什么原因,可能是英语转换成汉语让人误解的问题吧,这儿不详细讨论这个了。但是知道Monitor是用PV操作实现的。
先给出这张图和一个Mark Word的结构:
锁状态 | 25bit | 4bit | 1bit | 2bit | |
23bit | 2bit | 是否偏向锁(是否禁用偏向) | 锁标志位 | ||
无锁态 | 对象的hashCode | 分代年龄 | 0 | 01 | |
轻量级锁 | 指向栈中锁记录的指针 | 00 | |||
重量级锁 | 指向互斥量(重量级锁)的指针 | 10 | |||
GC标记 | 空 | 11 | |||
偏向锁 | 线程ID | Epoch | 分代年龄 | 1 | 01 |
1、重偏向是做什么的?
2、在已经初始锁定的情况下 的偏向锁为什么还有锁定和 解锁?
3、撤销偏向,怎么知道对象 有没有锁定?
当时为了解除这些疑惑我baidu到了另外的解释:
HotSpot支持存储释放偏向锁,以及偏向锁的批量重偏向和撤销。这个特性可以通过JVM的参数进行切换,而且这是默认支持的。Unlock状态下MarkWord的一个比特位用于标识该对象偏向锁是否被使用或者是否被禁止。如果该bit位为0,则该对象未被锁定,并且禁止偏向;如果该bit位为1,则意味着该对象处于以下三种状态:
基于偏向锁对象需要使用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,如此新的对象就可以被偏向锁定了。
获取偏向锁的步骤:
从上面,我对于
4、可重偏向这个状态是做什么的,为什么 Epoch的字段无效,还有就是epoch字段有什 么作用?
对于这个过程:
5、为什么校验epoch位,如果 InstanceKlass的prototype的MarkWord的 Epoch和对象的MarkWord的epoch位不 匹配表示 偏向已过期,可以重新偏向?
6、为什么在校验owner线程,比较偏向 线程ID与当前线程ID如果不匹配,还要 尝试使用CAS指令获取偏向,并且如果 成功了还表示偏向成功?
根据这两个还是没有从这些疑惑中找到相同点,寻找到合理的理解,公司可以,所以我google了下,查到一篇博客:
https://pdfs.semanticscholar.org/edf9/54412a9b1ce955bea148199f325759779540.pdf
从中知道了原来在偏向锁“可重偏向”是为了“在一个时间段内每一个时刻都是只有一个线程使用同一个对象,但是不是每一时刻都是同一个线程”而。
7、这个里面为什么 有类型的epoch和 实例对象的epoch?
8、为什么要自增类型C的epoch的值?
9、为什么只更新当前阻塞的线程锁住了的类型 C的实例对象的epoch?
最后详细的品味,详细的想终于将一切想通了。
1、重偏向是做什么的?
2、在已经初始锁定的情 况下的偏向锁为什么还 有锁定和解锁?
3、撤销偏向,怎么知道 对象有没有锁定?
4、可重偏向这个状态是 做什么的,为什么Epoch 的字段无效,还有就是 epoch字段有什么作用?
5、这个里面为什么 有类型的epoch和实 例对象的epoch?
6、为什么要增加类 型C的epoch的值?
7、为什么只更新当 前阻塞的线程锁住了 的类型C的实例对象 的epoch?
8、为什么校验epoch位,如果 InstanceKlass的prototype的MarkWord的 Epoch和对象的MarkWord的epoch位不 匹配表示 偏向已过期,可以重新偏向。
9、为什么在校验owner线程,比较偏向 线程ID与当前线程ID如果不匹配,还要 尝试使用CAS指令获取偏向,并且如果 成功了还表示偏向成功?
1、重偏向不是针对“ 在一个时间段内每一个 时刻都是相同线程使用 同一个对象”的情景,而 是在不同时间不同线程 使用相同对象的情景, “重偏向” 是就是为了 更新线程ID。
2、初始锁定也就是锁定,当然也有解锁。标志当前对象是否被线程占用。
3、不是有个锁定和解锁吗,它就是通过这个知道的。
4、可重偏向这个状态也 是针对“不同时间不同线 程使用相同对象的情景” 的情景,当对象中的epoch 和InstanceKlass中的epoch 值不相同的时候,说明这 个对象没有其它线程使用 了,所以对象的epoch字 段无效,下一个线程可以 继续使用,并且偏向。 Epoch其实就是为了标志 对象是否被线程锁住。
5、因为需要知道有 哪些类型C的实例对象 被阻塞的线程锁住 了,因为满足这个条 件的说明是活动的, 如果其他线程在这个 时候来发生竞争,那 么就CAS失败,进入 轻量级锁。
6、因为为了让上次 锁住的对象但是这次 已经不再有线程偏向 区别开。
7、这个问题上面 已经回答了,因为 阻塞的就是正在发 生偏向的,偏向还 没有失效。失效了 的可以重偏向。
8、校验epoch位是为了知道该对象 是否处于活动状态,如果确认和 InstanceKlass不相同,说明上次锁住这 个对象偏向的线程已经不再存活,可以 重新偏向了。
9、因为校验ower线程,比较当前线程 ID和对象里面的ID如果不相等,还有 可能可以发生重偏向,它的场景是“ 在一个时间段内每一个时刻都是只 有一个线程使用同一个对象,但是不 是每一时刻都是同一个线程”。
下面是我在网上找到的流程图,这里面完美总结了,只不过倒数3位不但是否偏向,而且表示是否禁用偏向:
下面附上偏向锁升级为轻量级锁的步凑 :
1、到全局安全点的时候才停止所有线程。
2、偏移所有者的堆栈被遍历,并且这个锁记录锁关联的对象将被 填充成如果这个对象发生轻量级锁生成的值。
3、对象的标记字被更新为指向堆栈上最古老的关联锁记录 。
4、释放在安全点阻塞的线程。
偏向锁没有那么简单