JVM锁优化

1.锁优化

  • 挂起线程和恢复线程的开销较大,对于锁定状态时间较短的情况下,挂起线程并不值得。
  1. 自旋锁与它的自适应自旋
    • 遇到锁不会挂起,而是忙循环(自旋)一会儿,避免了一次线程切换的开销,但是仍在占用CPU时间。
    • 1.6默认开启,默认自旋10次。
    • 1.6还引入了自适应自旋锁,他可以根据上一次在同一个锁上的自旋时间调整自旋次数。
    • 自旋失败则进入正常的挂起线程。
  2. 锁消除
    • JIT即时编译器在运行时如果发现某块代码上有同步,但是检测到该共享区域不可能存在竞争,就会进行锁消除。
    • 如对某个局部变量操作时加了锁,但是局部变量不可能逃逸出方法,所以2个线程不可能对同一个局部变量存在竞争。此时就会消除锁。
    • JIT编译说明这段代码是热点代码,消除锁之后对性能提高有所帮助。
  3. 锁粗化
    • 对一个对象反复的加锁解锁。如StringBuff的append()方法,编译器会优化到最外面一个锁。
  4. 轻量级锁
    • jvm实现无竞争情况下使用CAS操作消除同步使用的互斥量。
    • 轻量级锁为什么可以提高性能就是因为 “绝大多数部分的锁在整个同步周期内都是不存在竞争的”,这是一个经验数据,如果不存在竞争使用CAS操作就可以避免使用同步互斥量的开销。
  5. 偏向锁
    • 轻量级锁是无竞争情况下使用CAS操作取消使用同步互斥量,而偏向锁是无竞争情况下取消整个同步,连CAS都不用做。
    • 偏向锁会偏向第一个获得他的线程,如果该锁没有被其他线程获取,那么持有偏向锁的线程就不会进行同步。

2.对象头

  • 我们所说的某个对象的锁其实就是该对象的对象头中的几个标志位,该标志位改变为某个值说明该对象的锁被线程拿走了,释放锁后标志位恢复。
  • 对象头分为2部分,第一部分存储对象自身运行时数据,第二部分存储类型指针。
  1. 自身运行时数据(Mark Word):如哈希码、GC分代年龄、锁转态标志、线程持有的锁,偏向线程ID、偏向时间戳。Mark Word是非固定的数据结构,以便存储更多信息,根据对象状态不同各个信息所占位数会变化,但总体肯定是8字节倍数。32位机下Mark Word占32bit,64位机下占64bit。
  2. 类型指针:指向元数据(Class类数据)的指针
  3. 如果对象是数组那么对象头还有一块用于记录数组长度的区域,因为普通Java对象通过元数据可以确定大小,而数组的元数据无法确定数组大小。

2.1 Mark Word 的不同状态下存储不同内容。

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

 

 

 

 

 

 

 

2.2 不同状态下不同信息所占的位数和位置

3.轻量级锁的实现和加锁过程。

  • 程序并不是一遇到同步代码块立刻就拿到对象的重量级锁。
  1. 加锁

    1. 代码进入同步块时如果锁对象的标志位时01那么虚拟机会在当前线程的栈帧中建立一个 锁记录 空间 Lock Record,用来存储锁对象目前的Mark Word 的拷贝。
    2. 然后执行CAS 操作 ,它会比较锁对象的Mark Word 与拷贝是否相等。如果相等执行3,如果不等执行5
    3. 如果相等将锁对象的Mark Word 中的存储内容替换为 指向栈帧中拷贝的指针,此时这个线程就获得了该对象的锁,并且对象的锁标志位改为00。
    4. 此时栈帧中的Mark Word 存储的内容是(对象的hashCode,分代年龄,偏向锁位)而锁对象Mark Word 存储的内容是(指向栈中锁记录的指针),而且前者锁标志位01,后者锁标志位00.
    5. 如果不相等首先检查锁对象Mark Word内容是否指向当前线程,如果指向执行6,如果没有指向执行7
    6. 如果指向说明当前线程已经拿到了锁,则可以进入同步代码块执行,比如同步方法中调用同步方法,并且使用的同一个锁对象。
    7. 如果没有指向当前线程说明锁已经被别的线程拿到了,此时锁标志位改为10,锁对象Mark Word内容换为指向互斥量(重量级锁synchronized)的指针,然后执行8
    8. 注意此时线程发现拿不到锁也不会立刻被挂起,它会加入自旋,如果自旋一定次数失败就会进入阻塞状态。
  2. 解锁

    1. 首先查看锁对象的Mark Word内容是否指向栈,如果是那么就交换两者,同步代码执行完成。
    2. 如果没有,说明有别的线程来拿过锁,所以要解除互斥量同时唤醒被挂起的线程。
    3. synchronized是不公平的不会按先后挂起顺序唤醒。

4.偏向锁

  1. -XX:+UseBiasedLocking 启用偏向锁,1.6默认
  2. 锁对象第一次被线程获得,会把标志位设为01,此时线程ID还是空,对象还未锁定
  3.  通过一次CAS操作会把获取到这个锁对象的线程ID存入锁对象的Mark Word。此时对象已被锁定。
  4. 之后持有偏向锁的线程进入同步代码就不需要任何同步操作了。
  5. 如果另一个线程来获取锁,那么偏向模式就结束,分别如图按2种不同情况作出不同反应。
posted @ 2018-09-09 17:06  Mibloom  阅读(341)  评论(0编辑  收藏  举报