批量重偏向
当只有一个线程反复进入同步块时,偏向锁带来的性能开销基本可以忽略,但是当有其他线程尝试获得锁时,就需要等到safe point时将偏向锁撤销为无锁状态或升级为轻量级/重量级锁。因此,JVM中增加了一种批量重偏向/撤销的机制以减少锁撤销的开销,而mark word中的epoch也是在这里被大量应用,如果业务场景存在大量多线程竞争,那偏向锁的存在不仅不能提高性能,而且会导致性能下降(偏向锁并不都有利,jdk15默认不开启)。
https://z.itpub.net/article/detail/B87A9918BBB38652E96040B0C50A87C9
==============
批量重偏向(bulk rebias)机制是为了解决第一种场景。1.一个线程创建了大量对象并执行了初始的同步操作,之后在另一个线程中将这些对象作为锁进行之后的操作。这种case下,会导致大量的偏向锁撤销操作。
以class为单位,为每个class维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操作时,该计数器+1,当这个值达到重偏向阈值(默认20)时,JVM就认为该class的偏向锁有问题,因此会进行批量重偏向。每个class对象会有一个对应的epoch字段,每个处于偏向锁状态对象的mark word中也有该字段,其初始值为创建该对象时,class中的epoch的值。每次发生批量重偏向时,就将该值+1。
具体的做法便是在每个类中维护一个 epoch 值,你可以理解为第几代偏向锁。当设置偏向锁时,Java 虚拟机需要将该 epoch 值复制到锁对象的标记字段中;
在宣布某个类的偏向锁失效时,Java 虚拟机实则将该类的 epoch 值加 1,表示之前那一代的偏向锁已经失效。而新设置的偏向锁则需要复制新的 epoch 值;
为了保证当前持有偏向锁并且已加锁的线程不至于因此丢锁,Java 虚拟机需要遍历所有线程的 Java 栈,找出该类已加锁的实例,并且将它们标记字段中的 epoch 值加 1。该操作需要所有线程处于安全点状态;
在重偏向的过程,首先会对类中 mark word 的 epoch 值进行自增,对于已经被线程释放的锁对象,再次去申请锁时发现锁对象中的 epoch 和 类中的 epoch 不一样则自动进行重偏向。也不会执行撤销操作,而是直接通过CAS操作将其mark word的Thread Id 改成当前线程Id。
然后,会遍历所有线程的栈,看能否找到 klass类型锁对象 关联的锁记录,能找到说明该锁对象还在被占用,此时更新锁对象标记字中的 epoch 值为类中值,保证其继续持有锁。也就是只对已经被释放锁进行重偏向,还未被释放的锁不能重偏向。
https://www.jianshu.com/p/4758852cbff4
======
因为偏向锁的撤销大部分情形都是在安全点下执行的,安全点同步会导致系统停顿,整体性能损耗较高,所以当个某一类Klass对应的锁对象oop被累计撤销一定次数后就会触发批量重偏向,这个次数通过BiasedLockingBulkRebiasThreshold参数控制,默认是20。批量重偏向会先将Klass的prototype_header中的epoch值加1,然后遍历所有JavaThread的所有栈帧,遍历每个栈帧中包含的BasicObjectLock,如果其关联的锁对象oop是该Klass,则增加该oop的对象头中的epoch值,遍历完成后将触发批量重偏向的这个锁对象oop重新偏向给当前线程。注意此时不在栈帧中的即未被实际占用的锁对象的oop的epoch值就不会改变,重新获取该锁对象oop的偏向锁时因为epoch值不一致就可以重新被其他线程抢占,即提高偏向锁oop的使用率。
https://blog.csdn.net/qq_31865983/article/details/105024397
浙公网安备 33010602011771号