Java-对象头

Java对象的内存布局包括:对象头(Header),实例数据 (Instance Data)和补齐填充(Padding)

其中对象头中也包含了java内置的锁机制

对象头

对象头都至少包括两个字:

第一个字被称为Mark Word。包含了锁相关的信息

第二个字是指向metadata class的指针,metadata class定义了对象的类型(是哪个对象的实力)。其中也包含了VMT(Virtual Method Table)

(若对象是数组,还会存储数组的长度)

Mark Word的结构如下

根据锁标志位以及是否偏向锁分为:无锁态,偏向锁,轻量级锁,重量级锁,GC标记

轻量级锁

锁的状态分为:无锁态,偏向锁,轻量级锁,重量级锁。随着锁的竞争,锁可以从偏向升级到轻量,再升级到重量。

轻量级锁的形成

代码进入同步块时,如果对象为无锁状态(锁标志位为‘01’,是否为偏向锁为‘0’),虚拟机会在当前的线程的栈帧中建立一个锁记录(Lock Record)的空间,存储当前对象目前的Mark Word的拷贝(Displaced Mark Word)。(到达左图状态)

拷贝成功后,虚拟机将使用CAS操作,将对象的Mark Word更新为Lock Record的指针,并将Lock Record中的owner指向对象的Mark Word(到达右图状态)

若更新成功:该线程就拥有了 改对象的锁,并且对象Mark Word的锁标志位设置为‘00‘(即切换成轻量级锁状态)

若更新失败:虚拟机会检查对象的Mark Word是否指向当前线程的栈帧:

                      是的话说明该线程已经获得该对象的锁,可以进入同步块。

                      否则说明存在多个线程竞争锁,轻量级锁升级为重量级锁,锁状态为’01’,后面等待锁的线程进入阻塞,当前线程尝试自旋来获取锁。

 

轻量级锁的释放

把锁对象的Mark Word和线程的栈帧中复制的Mark Word替换回来

成功则同步完成

失败则说明有其他线程尝试获取该锁,释放锁的同时唤醒挂起的线程

偏向锁

引入偏向锁是为了在无多线程竞争时减少不必要的轻量级锁,轻量级锁在获取及释放时会多次使用CAS原子指令,而偏向锁只在置换Thread ID时依赖一次CAS原子指令

偏向锁获取过程

1.访问Mark Word中偏向锁的标志为‘1’,锁标志位为‘01’则为可偏向状态

2.若为可偏向状态,测试线程ID是否为当前线程,是则执行5,否则执行3

3.若线程ID未指向当前线程,通过CAS操作竞争锁:若竞争成功,则将Mark Word中线程ID置为当前ID,执行5。若竞争失败,执行4

4.若获取偏向锁失败,表示有竞争。到达全局安全的(safe point)时,获取偏向锁的线程被挂起,偏向锁升级为轻量级锁,被阻塞在安全点的线程继续执行同步代码

5.执行同步代码

偏向锁的释放

    在偏向锁获取过程的第4步可以看到,偏向锁只有遇到其他线程尝试竞争时,持有偏向锁的线程才会释放锁。偏向锁需要等待全局安全点,它会暂停拥有偏向锁的线程,判断对象是否处于锁定状态,撤销偏向锁以后,恢复到未锁定(标志为‘01’)或轻量级(‘00’)的状态

偏向锁,轻量级锁,重量级锁 状态转换图

 参考资料

https://blog.csdn.net/zhoufanyang_china/article/details/54601311

https://blog.csdn.net/codershamo/article/details/52071996

https://www.cnblogs.com/dugk/p/8900856.html

原文地址:https://www.cnblogs.com/lmhyhblog/p/10773352.html