JVM中对象头信息内容
对象头信息是与对象自身定义的数据无关的额外存储成本,HotSpot虚拟机中的对象头分为两部分信息
第一部分用于存储对象自身的运行时数据,如哈希码,GC分代年龄等,这部分数据在32位和64位的虚拟机中的长度分别为32bit和64bit,官方称这部分为“Mark Word”,它也是实现轻量级锁和偏向锁的关键。考虑到虚拟机的空间效率,Mark Word本设计成一个非固定的数据结构以便在极小的空间内存中存储尽量多的信息,它会根据对象的状态复用自己的存储空间。
存储内容标志位状态是否偏向
对象哈希码,对象分代年龄01未锁定0
指向锁记录的指针00轻量级锁定
指向重量级的指针10膨胀(重量级锁定)
空,不需要存储记录11GC标记
偏向线程ID,偏向时间戳,对象分代年龄01可偏向1
此处有疑问,轻量级锁的时候,Mark Word中的内容被复制到栈帧中,Mark Word存储的是内容所在栈帧的地址,那重量级锁存储的是和Mark Word内容无关的互斥量地址,此时的Mark Word中存储的哈希码等信息存放在哪里。
第二部分用于存储指向方法区对象类型数据的指针,如果是数组的话,还会有一个额外的部分用于存储数组长度
下面介绍轻量级锁和可偏向锁
轻量级锁
首先需要说明轻量级锁不是重量级锁的替代方案,其目的是在没有多线程竞争的前提下,减少传统重量级锁使用操作系统互斥量产生的性能消耗(使用互斥量会阻塞线程,使线程挂起,和唤醒,这两个操作都需要切换到操作系统内核态执行)
轻量级锁执行过程:在代码进入同步块的时候,如果此同步对象没有被锁定(标志位01),虚拟机首先将在当前线程的栈帧中建立一个锁记录的空间,用于存储锁对象目前的Mark Word拷贝—Displaced Mark Word,这个时候锁对象的Mark Word就存储在当前线程的栈帧中;然后虚拟机将使用CAS(操作系统级别保证该操作的原子性)操作尝试将对象头中的Mark Word内容更新为指向存储当前锁对象Mark Word内容的当前想进入同步块的线程栈帧的地址,如果这个更新成功,那么这个线程就拥有了该对象的锁,然后回写锁对象头的Mark Word中标志位为00
如果这个CAS操作失败,虚拟机首先会检查锁对象的头部Mark Word是否指向当前线程的栈帧,如果是说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明这个锁对象已经被其他线程抢占了。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,标志位变为10,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。
以上是轻量级锁加锁过程,解锁过程也是使用CAS,只是将锁对象当前的Mark Word中的内容从线程栈帧地址替换为线程栈帧Displaced Mark Word的内容,如果替换成功,整个同步过程就结束了;如果替换失败,说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。
轻量级锁能够提升程序同步性能的依据是,大部分场景在同步周期内没有锁竞争,这个经验数据。如果没有竞争,轻量级锁使用CAS操作避免使用互斥量的开销,但是如果存在锁竞争,除了互斥量的开销还有CAS操作,因此会比重量级锁更慢
本处有一个疑问:CAS操作成功后获取到轻量级锁,后续其他线程试图CAS都会失败,所以理论上获取锁的线程CAS释放锁应该都会成功,怎么会失败????
偏向锁
偏向锁目的是在无竞争情况下,消除同步原语(不管是基于互斥量的还是底层CAS操作的支持的同步原语),
偏向锁意思是这个锁会偏向于第一个获取它的线程,如果在接下来的执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程将永远不需要进行同步。
如果当前虚拟机启动了偏向锁,那么当锁对象第一次被线程获取的时候,虚拟机会将锁对象头的标志位设置为01,同时使用CAS操作把获取到这个锁的线程ID记录在锁对象的对象头Mark Word中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作。
当有另一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定状态,如果之前线程锁定过偏向锁,目前没有锁定了,那就恢复到未锁定状态,如果偏向锁线程持有者还锁定着,那就升级到轻量级锁定状态,后续的同步操作就和上面的轻量级锁一样。
是啥
网友评论