目录
- 1. 对象信息的存储
- 1.1. 对象头
- 1.2. 属性信息
- 1.3. 对齐字节
- 2. 对象头中的MarkWord
- 3. synchronized锁升级机制
- 3.1. 无锁状态 -> 偏向锁状态
- 3.2. 偏向锁 -> 轻量级锁
- 3.3. 轻量级锁 -> 重量级锁
1. 对象信息的存储
对象的信息实际上是存在与堆中的,而方法区中则是存类的信息。
为什么非要8的整数倍?
答:可以保证内存的利用率
1.1. 对象头
- MarkWord
- 类指针
- 数组长度
1.2. 属性信息
存储属性相关信息
1.3. 对齐字节
保证一个对象的大小必须是8的整数倍
为什么非要8的整数倍?
答:可以保证内存的利用率
在JVM垃圾回收的时候,会产生很多内存碎片,比如一个对象大小是7,被回收后留下一个长度位7的空间,再来一个对象大小为8,就利用不了这个空间,所以要尽可能保证对象的内存大小一致,提高内存的复用。
2. 对象头中的MarkWord
MarkWord是sync实现的关键点
对于每一个刚new出来的对象,它的结构是这样的
我们注意几个关键字,age,biased_lock,lock。
age是表示这个对象的年龄,也就是JVM垃圾回收里面的新生代老年代,age占四位,所以分代年龄的最大值是15。
biased_lock和lock是和对象锁有关的东西
- 对象的锁状态对应编码表示
对象产生后,过一段时间,会自动从正常状态变成偏向锁
偏向锁:只能一个线程使用,该线程不用获取锁,拿到锁的效率高。
当偏向锁发生率别的线程对它的争抢,那么java虚拟机会将这个偏向锁升级为轻量级锁
3. synchronized锁升级机制
在每一个对象被创建的时候,都会同步产生另一个对象,叫做Monitor(对象监视器),当一个线程获取这个对象锁的时候,实际上是获取了这个对象的对象监视器,一个对象只有一个对象监视器,被一个线程获取到后,其余线程就获取不到了。这种状态效率很低
所以在JDK1.6后,对sync做了优化(锁升级),当sync升级为重量级锁,才是获取monitor的状态
当一个对象一直只有一个线程访问的时候,在JDK1.6前,每一次的访问,也会获取monitor和释放monitor,这让程序效率很低,所以在JDK1.6后,实现了偏向锁,当一个对象只有一个线程访问的时候,那这个线程在访问这个对象的时候不需要获取锁。
3.1. 无锁状态 -> 偏向锁状态
当一个对象的锁,在一段时间内,总是只有一个线程会获取,那么这个对象的锁就会升级为偏向锁,偏向锁是只有这一个线程可以使用,省掉了moniter的一个切换,效率提高。
3.2. 偏向锁 -> 轻量级锁
当有另一个线程同样来获取一个对象的偏向锁的时候,发现那个锁已经被获取率,而且只能是markword线程ID的那个线程才能获取,所以就会暂停持有偏向锁的那个线程,并且撤销掉该对象的偏向锁。
我们从上面的图可以发现,当偏向锁转换为轻量级锁的时候,markword的值发生了极大的变化,而且锁迟早也会被释放掉,变成正常状态,所以我们需要把正常状态下的markword存储起来,再更改markword的值
轻量级锁的前62位,存储的就是lock record的地址
一旦升级后,这个锁永远只能是轻量级锁(只能向上升级,不能向下降级)
其实在JDK1.6后,实现的这个锁升级机制,原理就是CAS对markword的比较替换来实现的,比JDK1.6前,直接获取moniter的效率要好多了。
3.3. 轻量级锁 -> 重量级锁
当一开始就发生锁的争抢的时候,这个对象的状态会被直接升级为轻量级锁
线程2尝试获取轻量级锁,发现markword已经不是正常状态下了,已经被其它线程修改成轻量级锁了,所以就会一直CAS自旋等待markword变回正常状态,自己再去替换(在一定时间内,线程2替换成功,锁不会升级)
自旋就是一个死循环的状态,这样的状态特别消耗cpu,所以自旋状态的保持会有一个时间段,过了这个时间段后,自旋状态的这个线程会把markword升级为重量级锁
重量级锁就是JDK1.6前的,获取对象的moniter。