jol分析锁膨胀流程
前置准备
-
对象头的markword的格式
-
引入依赖
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
JVM对偏向锁进行了优化,4s后才开启。在这里关闭或者手动休眠4s:
public static void main(String[] args) throws Exception {
//-XX:BiasedLockingStartupDelay=0
System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
TimeUnit.SECONDS.sleep(4);
System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
}
结果因为涉及到JVM的端序问题,所以应该反着才是真正的存储:
即:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001,第一个是偏向位,二三是标志位
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101,第一个偏向位,二三标志位
休眠太慢了,这里直接关闭偏向延迟时间,让创建对象的时候直接设置为匿名偏向锁。
-XX:BiasedLockingStartupDelay=0
偏向锁
加锁流程:
- 最开始无锁状态,一个线程进入同步块,先检查是不是偏向锁【锁标志01,偏向标志1】。
- 检查
偏向线程id==自己
- 如果是,无需CAS,执行同步块。
- 如果不是,CAS替换为自己;替换成功执行同步块;替换失败执行锁撤销。
锁撤销流程:
- 等待全局安全点,暂停持有锁的线程
- 如果已经退出同步块,将锁设置成无锁状态。
- 如果没有退出同步块,将锁升级为轻量级锁。
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
synchronized (o){//注意关闭逃逸分析,不然JVM会将这个代码优化为没有加锁//
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
TimeUnit.SECONDS.sleep(1000);
new Thread(()->{
synchronized (o){//注意关闭逃逸分析,不然JVM会将这个代码优化为没有加锁//
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}).start();
结果:
轻量锁【偏向锁撤销可能会升级轻量锁】
根据锁撤销流程,可能锁会升级成轻量锁;
轻量锁的加解锁流程:
- 访问代码块前,需要在栈帧创建lock record拷贝一份mw。
- 获取锁时,需要通过CAS将对象头的mw改为lr的指针,写入成功则锁获取成功;失败则自旋,自旋超过最大次数升级为重量级锁。
- 释放锁时,通过CAS操作将mw从lr写入对象头,写入成功则锁释放,写入失败则存在竞争【正在释放,一个线程升级重量锁】,膨胀为重量级锁。
static Object a = new Object();
public static void main(String[] args) throws Exception {
Object o = new Object();
System.out.println("匿名偏向锁:"+ClassLayout.parseInstance(o).toPrintable());
Thread t1 = new Thread(() -> {
synchronized (o) {
System.out.println("t1" + ClassLayout.parseInstance(o).toPrintable());
}
});
t1.start();
t1.join();
System.out.println("并未主动撤销锁:"+ClassLayout.parseInstance(o).toPrintable());
Thread t2 = new Thread(() -> {
synchronized (o) {
System.out.println("t2:" + ClassLayout.parseInstance(o).toPrintable());
}
});
t2.start();
t2.join();
System.out.println("锁撤销完毕:"+ClassLayout.parseInstance(o).toPrintable());
}
尽管t2执行时,t1已经完成了,t2尝试获取,可能会进行锁撤销,然后获取锁。但是由于撤销的时机由JVM决定,所以该程序有两种结果。
当运行到一个下全局安全点的时候,暂停线程,根据此时线程同步块的执行来决定是升级还是设置为无锁。
这里是锁撤销时,升级为轻量级锁,轻量级锁执行完毕,将栈中mk转入对象头。
重量级锁
Object o = new Object();
new Thread(()->{
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}).start();
new Thread(()->{
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}).start();
因为这里的jol打印对象头已经很耗时了,所以不用自己手动休眠了。