HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Object Header)、实例数据(Instance Data)和对齐填充(Padding)。

对象头(Object Header)

 JVM的对象头包括二/三部分信息:1、Mark Word;2、 类型指针;3、数组长度(只有数组对象才有)

 1、Mark Word

用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等,这部分数据的长度在32位和64位的虚拟机(暂 不考虑开启压缩指针的场景)中分别为32个和64个Bits。

Mark Word在不同的锁状态下存储的内容不同,在32位JVM中是这么存的:

锁状态25bit4bit1bit2bit
23bit2bit是否偏向锁锁标志位
无锁对象的HashCode分代年龄001
偏向锁线程IDEpoch偏向时间戳分代年龄101
轻量级锁指向栈中锁记录的指针00
重量级锁指向重量级锁的指针10
GC标记11

其中无锁和偏向锁的锁标志位都是01,只是在前面的1bit区分了这是无锁状态还是偏向锁状态。

2、 类型指针 

即是对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。 并不是所有的虚拟机实现都必须在对象数据上保留类型指针,即查找对象的元数据信息并不一定要经过对象本身。

3、数组长度 

如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。 

偏向锁

 引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要多次CAS获取锁的执行过程,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子操作(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。偏向锁则是在只有一个线程或者多个线程不同步执行同步块时进一步提高性能。

轻量级锁

轻量级锁是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景,如果线程阻塞需要线程之间完成线程间的通信,耗费性能(用户态和内核态状态切换),所以直接让线程进入自旋等待锁释放。

1、偏向锁获取过程:

  (1)访问Mark Word中偏向锁的标识位(1bit)是否设置成1,锁标志位(2bit)是否为01——确认为可偏向状态。

  (2)如果为可偏向状态,则偏向锁线程ID是否指向当前线程,如果是,进入步骤(4),否则进入步骤(3)。

  (3)如果偏向锁线程ID并未指向当前线程,因为偏向锁不会主动释放,所以当前线程可以看到对象时偏向状态以及拥有该对象锁的线程,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新CAS操作偏向新的线程,然后执行(4);如果原来的线程依然存活,由偏向锁升级为轻量级锁(在下面进行分析)。

  (4)执行同步代码。

2、偏向锁升级为轻量级锁过程

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,需要查看Java对象头中记录的偏向锁的线程是否存活,如果没有存活,那么修改对象头偏向锁标志位为0,其它线程可以竞争将其设置为偏向锁;如果存活,拷贝对象头中的Mark Word到该线程的栈帧中的锁记录里让lock record的指针指向锁对象头中的Mark Word,再让Mark Word指向指向lock record,唤醒线程继续执行原来线程的同步代码,则当前线程通过CAS操作竞争锁,竞争失败执行自旋操作继续竞争锁。

  ps: 全局安全点safepoint 参考 理解JVM的safepoint

3、轻量级锁获取过程 

线程的栈帧中有一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。 

线程会拷贝对象头中的Mark Word到锁记录(Lock Record)中,然后使用CAS操作尝试将锁对象的Mark Word指针指向Lock Record,并将线程栈帧中的Lock Record里的owner指针指向Object的 Mark Word。如果更新成功,则表示获取了轻量级锁。

4、轻量级锁膨胀重量级锁过程

a线程获得锁,会在a线程的栈帧里创建lock record(锁记录),让lock record的指针指向锁对象的对象头中的mark word.再让mark word 指向lock record.这就是获取了锁。如果b线程在锁竞争时,发现锁已经被a线程占用,则b线程不进入内核态,让b线程自旋,执行空循环,等待a线程释放锁。如果发现a线程没有释放锁,这时候c线程也来竞争锁,那么这个时候轻量级锁就会膨胀为重量级锁。

 

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐