JVM学习笔记10:锁优化

一.自旋锁与自适应自旋

  1. 自旋锁:如果线程可以很快获得锁,那么可以不在OS层挂起线程,而是让线程做几个忙循环,这就是自旋。
  2. 自适应自旋:自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间和锁的拥有者状态来决定。
  3. JDK1.7以上已经是内置实现,默认开启。
  4. 如果锁被占用时间很短,自旋成功,那么能节省线程挂起、以及切换时间,从而提升系统性能

如果锁被占用时间很长,自旋失败,会白白耗费处理器资源,降低系统性能。

二.锁消除

       虚拟机即时编译在运行时,对一些代码上要求同步,但是被逃逸分析检测到不可能存在共享数据竞争的锁进行消除。

  1. 通过-XX:+EliminateLocks来开启
  2. 同时要使用-XX:+DoEscapeAnalysis开启逃逸分析,所谓逃逸分析。

      (1)如果一个方法中定义的一个对象,可能被外部方法引用,称为方法逃逸
      (2)如果对象可能被其它外部线程访问,称为线程逃逸,比如赋值给类变量或者可以在其它线程中访问的实例变量

      

三.锁粗化 

      通常我们都要求同步块要小,但一系列连续的操作导致对一个对象反复的加锁和解锁,这会导致
不必要的性能损耗。这种情况建议把锁同步的范围加大到整个操作序列

四.轻量级锁

轻量级是相对于传统锁机制而言,其本意是没有多线程竞争的情况下,减少传统锁机制使用OS实现互斥所产生的性能损耗。

1:其实现原理很简单,就是类似乐观锁的方式
2:如果轻量级锁失败,表示存在竞争,升级为重量级锁,导致性能下降。

 

HotSpot虚拟机的对象头(Object Header)分为两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄(Generational GC Age)等,这部分数据的长度在32位和64位的虚拟机中分别为32个和64个Bits,官方称它为“Mark Word”,它是实现轻量级锁和偏向锁的关键。另外一部分用于存储指向方法区对象类型数据的指针,如果是数组对象的话,还会有一个额外的部分用于存储数组长度。

Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。例如在32位的HotSpot虚拟机中对象未被锁定的状态下,Mark Word的32个Bits空间中的25Bits用于存储对象哈希码(HashCode),4Bits用于存储对象分代年龄,2Bits用于存储锁标志位,1Bit固定为0,在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下Mark Word的存储内容如下表所示。

存储内容 标志位 状态
对象Hash值、对象分代年龄 01 未锁定
指向锁记录的指针 00 轻量级锁定
指向重量级锁的指针 10 膨胀(重量级锁定)
空,不记录信息 11 GC标记
偏向线程ID、偏向时间戳、对象分代年龄 01 可偏向

 

轻量级锁加锁步骤:

  1. 代码进入同步块的时候,对象没有被锁定(对象头Mark Word 中的锁标志位为“01”)。
  2. 在当前线程栈帧中建立一个名为锁记录(Lock Record)的空间,来存储对象的MarkWord的拷贝。
  3. 虚拟机使用CAS操作尝试将对象Mark Word 更新为指向Lock Record的指针,若成功则线程拥有该对象的锁。
  4. 更新Mark Word 的锁标志位为“00”。

如果上面的步骤失败。则虚拟机首先会检查对象的MarkWord 是否指向当前线程的栈帧,如果是,说明当前线程已经拥有了对象的锁,那就可以直接进入同步块继续执行。否则说明这个对象已经被其他线程抢占了,若是多条线程争用同一个锁,则轻量级锁不在有效,要膨胀为重量级锁。锁标志位值要变为“10”。

五.偏向锁

偏向锁是在无竞争情况下,直接把整个同步消除了,连乐观锁都不用,从而提高性能。

  1. 所谓的偏向,就是偏心,即锁会偏向于当前已经占有锁的线程
  2. 只要没有竞争,获得偏向锁的线程,在将来进入同步块,也不需要做同步
  3. 当有其它线程请求相同的锁时,偏向模式结束
  4. 如果程序中大多数锁总是被多个线程访问的时候,也就是竞争比较激烈,偏向锁反而会降低性能
  5. 使用-XX:-UseBiasedLocking来禁用偏向锁,默认是开启的
  6. 使用-XX:BiasedLockingStartupDelay来设置JVM启动后多长时间启动偏向锁
     

你可能感兴趣的:(JVM)