Java对象头(不用记内容,只需要知道,对象头里面,会保存一些信息)
锁存在Java对象头里。如果对象是数组类型,则虚拟机用3个Word(字宽)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,一字宽等于四字节,即32bit。(下面这个表格讲的很清楚)
长度 |
内容 |
说明 |
32/64bit |
Mark Word |
存储对象的hashCode或锁信息等。 |
32/64bit |
Class Metadata Address |
存储到对象类型数据的指针 |
32/64bit |
Array length |
数组的长度(如果当前对象是数组) |
Java对象头里的Mark Word里默认存储对象的HashCode,分代年龄和锁标记位。32位JVM的Mark Word的默认存储结构如下:
25 bit |
4bit |
1bit 是否是偏向锁 |
2bit 锁标志位 |
|
无锁状态 |
对象的hashCode |
对象分代年龄 |
0 |
01 |
在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下4种数据:
锁状态 |
25 bit |
4bit |
1bit |
2bit |
||
23bit |
2bit |
是否是偏向锁 |
锁标志位 |
|||
轻量级锁 |
指向栈中锁记录的指针 |
00 |
||||
重量级锁 |
指向互斥量(重量级锁)的指针 |
10 |
||||
GC标记 |
空 |
11 |
||||
偏向锁 |
线程ID |
Epoch |
对象分代年龄 |
1 |
01 |
在64位虚拟机下,Mark Word是64bit大小的,其存储结构如下:
锁状态 |
25bit |
31bit |
1bit |
4bit |
1bit |
2bit |
|
cms_free |
分代年龄 |
偏向锁 |
锁标志位 |
||||
无锁 |
unused |
hashCode |
0 |
01 |
|||
偏向锁 |
ThreadID(54bit) Epoch(2bit) |
1 |
01 |
偏向锁,简单的讲,就是在锁对象的对象头中有个ThreaddId字段,这个字段如果是空的,
第一次获取锁的时候,就将自身的ThreadId写入到锁的ThreadId字段内,将锁头内的是否偏向锁的状态位置1.
这样下次获取锁的时候,直接检查ThreadId是否和自身线程Id一致,如果一致,则认为当前线程已经获取了锁,因此不需再次获取锁,
略过了轻量级锁和重量级锁的加锁阶段。提高了效率
流程是这样的 偏向锁->轻量级锁->重量级锁
第一步,检查MarkWord里面是不是放的自己的ThreadId ,如果是,表示当前线程是处于 “偏向锁”
第二步,如果MarkWord不是自己的ThreadId,锁升级,这时候,用CAS来执行切换,新的线程根据MarkWord里面现有的ThreadId,通知之前线程暂停,
之前线程将Markword的内容置为空。
第三步,两个线程都把对象的HashCode复制到自己新建的用于存储锁的记录空间,接着开始通过CAS操作,
把共享对象的MarKword的内容修改为自己新建的记录空间的地址的方式竞争MarkWord,
第四步,第三步中成功执行CAS的获得资源,失败的则进入自旋
第五步,自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于 轻量级锁的状态,如果自旋失败
第六步,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己
- 锁消除
public String[] createStrings() { Vector<String> v = new Vector<>(); for (int i = 0; i < 100; i++) { v.add(Integer.toString(i)); } return v.toArray(new String[]{}); }