讲Java锁升级,首先讲一下Java对象的内存布局。
首先Java new 一个对象,生成的对象包括三部分信息。
- 对象头(Object Header)
- 运行时元数据(Mark Word)
- hashcode值
- GC年龄
- 偏向线程Id
- 偏向时间错
- 锁状态标志
- 线程拥有的锁
- 类型指针(Klass Word)
- 数据长度(数组对象才有的)
- 运行时元数据(Mark Word)
- 实例数据
- 对齐填充
因为对象的锁信息包含在对象头信息里面,所以在了解锁升级机制前最好了解一下对象的内存布局。
对于对象来说,有这么4种锁状态: 无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。
(这里以64位JVM为例子,这句话看不懂没关系,不影响你继续看下面)
首先,我们看下四种状态下,对象头(Object Header)中分别是什么样子,因为讲的是有关锁的,所以这里我主要讲一下 运行时元数据(Mark Word)中长什么样子。
在64位的JVM中,对象头整个 Object Header占128bit长度,Mark Word占 64,Klass Word占64。
数组对象的对象头是稍微有一点点不一样:
在64位的JVM中,数组对象的对象头整个 Object Header占 128bit+64bit 长度,加的64bit就是用来存数组长度的。
无锁状态
biase_lock:偏向锁标志
lock:锁标志
age:GC年龄
偏向锁
当前线程指针:偏向线程ID
Epoch:偏向时间戳
轻量级锁
指向线程中Lock Record的指针:这个东西又有点难解释了,要是完全不知道的可以跳过,你就认为是个占了62bit的指针跳过,问题不大。不过,我还是简单的解释下。Lock Record是对轻量级锁的优化,当解释器执行monitorenter字节码轻度锁住一个对象的时候,就会在虚拟机栈的栈帧中分配一个叫Lock Record的空间,这个空间用来存储这个被锁住的对象的MarkWord。(先解释到这了,这个深挖还有很多东西,下次有空再专门解释吧,这段里面很多JVM的相关知识,还是那句话,要是看不懂,就记住这是一个62bit的指针,就过。)
重量级锁
指向互斥量的指针:这个还是有点难解释,同上,完全不知道的,直接过。还是简单解释下,简单说就是在重量级锁状态下,指向对象监视器monitor的指针。就是说,重量级锁状态下,两个不同的线程如果同时对一个对象竞争,一个线程拿到了,一个线程没拿到,那么这个没竞争到对象的线程就会进入等待状态。这个monitor就是管理等待的线程的,而这个 指向互斥量的指针就是指向这个监视器monitor的。(要是这段没看懂,还是同上,就一个指针,就过。)
小小总结一下一个重点,锁标志:
无锁和偏向锁的锁标志都是:01 ,不同的是biase_lock,无锁是0,偏向锁是1
轻量级锁的锁标志是:00
重量级锁是:10
——————————————————————————————————————
无锁:001
偏向锁:101
轻量锁:00
重量锁:10
有没有人想过,一共四种锁状态,为什么不直接两个bit,来表示呢?这其实是因为还有个状态,就是GC状态,就不再这里解释了。算了,还是说一下吧,嫌啰嗦的可以跳过哦,GC状态就是最后两位为11,算了,说多说了那就再画个图吧。。。(一个挺啰嗦的程序员)
GC状态
CMS:就是一个垃圾回收器,这个就不解释了。
OK,到这,四种锁状态就简单解释完了。
现在开始说锁升级。。哎,显得有点啰嗦了,希望能耐心读完吧。
锁升级
一个对象刚刚被new出来的时候,就是无锁状态,001(偏向锁标志0,锁标志01)。
当线程来上锁了,首先是由无锁状态变为偏向锁,偏向线程ID会改为这个线程的ID。
如果有线程来竞争了,偏向锁会被撤销,升级到轻量级锁,线程会在自己虚拟机栈的栈帧中生成LockRecord,这里会用到CAS将MarkWord设置为指向自己这个象出的LockRecord的指针,谁成功设置了,谁就得到锁。
如果竞争严重了(线程自旋转了10次或者自旋的线程数目超过CPU核数的一半)jdk1.6之后,升级到重量级锁。
先说到这里了,宿舍十点半关门,不知道大家的学校是不是这种规定,反正我是。。哈哈,先这样了。
如有错误,欢迎指正~