• 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种不同情况作出不同反应。
  • 相关阅读:
    sublime使用及插件
    Unity 查找
    Unity 3D 的四种坐标系
    C#知识点<4>
    C#知识点<3>
    C#知识点<2>
    排序算法
    OOP的三大特性------封装、继承、多态
    C#常用函数
    C++-------------类和对象
  • 原文地址:https://www.cnblogs.com/mibloom/p/9613598.html
Copyright © 2020-2023  润新知