继续承接上一篇博客一、从Java对象头看synchronized锁的状态
import org.openjdk.jol.info.ClassLayout;
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
A a = new A();
/// 理论上说这里应该是无锁状态
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
运行结果:
mark_word 最后3bit 是 001 ,表示 无锁,且不可偏向
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
A a = new A();
System.out.println("a对象的hashcode:16进制:"+Integer.toHexString(a.hashCode()));
System.out.println("a对象的hashcode:2进制:"+Integer.toBinaryString(a.hashCode()));
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
运行结果:
可以看出: mark_word 最后3bit 是 001 ,表示 无锁,且不可偏向。这时候一旦进行对象的hashcode计算,那么hashcode就会存储在mark_word 的第26–56的位置,共占 31个bit,前面25bit为0,不存任何信息。
由此可见,对象的hashcode是延迟加载的。
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);
A a = new A();
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
运行结果:
从运行结果可以看出:mark_word 最后3bit 是 101,而且前面54bit为0,说明a是一个无锁且可偏向状态。
为什么sleep了5秒钟,a对象的状态就和之前的不一样了呢?从一个不可偏向的无锁变成了一个可偏向的无锁!
JVM的偏向锁其实是可以进行开启和关闭的,偏向锁虽然是默认启用的,但是它在应用程序启动几秒钟之后才激活
这就解释了,为什么main方法中如果sleep一段时间,new出来的a对象,是一个无锁的可偏向状态,因为这个时候jvm已经激活了偏向锁机制。
我们可以通过使用JVM参数来关闭延迟 -XX:BiasedLockingStartupDelay=0
,这样一来,程序在启动的时候new出来的对象就是可偏向状态的无锁对象。
当然还可以JVM参数关闭偏向锁-XX:-UseBiasedLocking
通过JVM的 参数 -XX:+PrintFlagsFinal可以查看出 偏向锁的启动延迟是4000微妙也就是4秒钟。
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);/// sleep 一段时间是为了让JVM激活偏向锁机制
A a = new A(); /// 现在new出来的对象默认是 可偏向的无锁状态 (101)
System.out.println("刚new出来:");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
/// 这里我们计算一下a对象的hashcode,然后再看一下a的状态是什么?
a.hashCode();
System.out.println("计算过hashcode后:");
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
运行结果:
从上面运行结果可以看出:计算一个对象的hashcode会使得一个对象从 可偏向无锁
变为不可偏向无锁
。
为了后面的测试方便,写了一个工具类,只输出java对象头的mark_word 部分而且对大小端进行了转换
package com.com.kinyang.jol;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import org.openjdk.jol.vm.VirtualMachine;
/**
* @author KinYang.Lou
* @date 2020/3/27 8:30 下午
*/
public class Print64JVMHeader {
static final String ISEMPTY = "00000000 00000000 00000000 00000000 00000000 00000000 000000";
/**
* 打印对象头的 mark word 信息
* @param instance
*/
static void toPrintMarkWord(Object instance){
ClassLayout classLayout = ClassLayout.parseInstance(instance);
VirtualMachine vm = VM.current();
/// 获取 对象头大小(字节)
long headerSize = (long) classLayout.headerSize();
// 这里只打印 mark_word 部分
if (headerSize<8)
return;
long markWordSize = 8;
String markWord = "";
for(long off = 0L; off < markWordSize; off += 1L) {
byte aByte = vm.getByte(instance, off); // 取一个字节
markWord = toBinary(aByte)+" "+markWord;
}
markWord = markWord.trim();
int length = markWord.length();
String b2 = markWord.substring(length-2); // 末2位
String e3 = markWord.substring(length-3,length-2);/// 倒数第三位
String i54 = markWord.substring(0,60); /// 去前面54bit数据
String lockType = "";
switch (b2){
case "11":
lockType = "被GC标记";
break;
case "10":
lockType = "重量级锁";
break;
case "00":
lockType = "轻量级锁";
break;
case "01":
{
if (e3.equals("0")){
lockType = "不可偏向&无锁";
}else{
if(ISEMPTY.equals(i54)){
lockType = "可偏向&无锁";
}else {
lockType = "偏向锁";
}
}
break;
}
default:
lockType = "出现错误";
}
System.out.println(instance.getClass().getName()+" 对象的mark_word:");
System.out.println(markWord + " --- ("+lockType+")");
}
private static String toBinary(byte x){
String s = Integer.toBinaryString((x & 0xFF) + 0x100).substring(1);
return s;
}
}
package com.com.kinyang.jol;
import org.openjdk.jol.info.ClassLayout;
/**
* @author KinYang Lau
* @date 2020/3/25 8:49 下午
*/
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);/// sleep 一段时间是为了让JVM激活偏向锁机制
ClassLayout classLayout = ClassLayout.parseInstance(Demo1.class);
A a = new A(); /// 现在new出来的对象默认是 可偏向的无锁状态 (101)
System.out.println("a 刚new出来:");
Print64JVMHeader.toPrintMarkWord(a);
synchronized (a){
System.out.println("a 在synchronized代码块中:");
Print64JVMHeader.toPrintMarkWord(a);
}
System.out.println("a 退出synchronized代码块:");
Print64JVMHeader.toPrintMarkWord(a);
a.hashCode();
System.out.println("a 经过一次hashcode计算:");
Print64JVMHeader.toPrintMarkWord(a);
}
}
运行结果
根据上图的运行结果可以看出:偏向锁在经过hashcode后,会变成不可偏向的无锁状态
package com.com.kinyang.jol;
import org.openjdk.jol.info.ClassLayout;
/**
* @author KinYang Lau
* @date 2020/3/25 8:49 下午
*/
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);/// sleep 一段时间是为了让JVM激活偏向锁机制
ClassLayout classLayout = ClassLayout.parseInstance(Demo1.class);
A a = new A(); /// 现在new出来的对象默认是 可偏向的无锁状态 (101)
System.out.println("a 刚new出来:");
Print64JVMHeader.toPrintMarkWord(a);
synchronized (a){
System.out.println("a 在synchronized代码块中,未进行hashcode计算:");
Print64JVMHeader.toPrintMarkWord(a);
System.out.println("a对象的hashcode:2进制: "+Integer.toBinaryString(a.hashCode()));
System.out.println("a synchronized代码块中,经过一次hashcode计算:");
Print64JVMHeader.toPrintMarkWord(a);
}
System.out.println("a 退出synchronized代码块:");
Print64JVMHeader.toPrintMarkWord(a);
}
}
运行结果:
从上图的运行结果可以看出来,如果在偏向锁未释放阶段,进行hashcode计算的话,锁会直接升级为重量级锁。不知道这里为什么不升级到轻量级锁,而直接到了重量级锁?
package com.com.kinyang.jol;
import org.openjdk.jol.info.ClassLayout;
/**
* @author KinYang Lau
* @date 2020/3/25 8:49 下午
*/
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);/// sleep 一段时间是为了让JVM激活偏向锁机制
ClassLayout classLayout = ClassLayout.parseInstance(Demo2.class);
A a = new A(); /// 现在new出来的对象默认是 可偏向的无锁状态 (101)
System.out.println("a 刚new出来:");
Print64JVMHeader.toPrintMarkWord(a);
synchronized (a){
System.out.println("\n 在 主线程中对 a 进行synchronized加锁:");
Print64JVMHeader.toPrintMarkWord(a);
}
System.out.println("\n a 退出线程的synchronized代码块:");
Print64JVMHeader.toPrintMarkWord(a);
/// 启动一个线程,对a进行加锁
new Thread("T1"){
@Override
public void run() {
synchronized (a){
System.out.println("\n 进入T1线程,对a进行synchronized加锁:");
Print64JVMHeader.toPrintMarkWord(a);
}
System.out.println("\n 退出T1线程代码块:");
Print64JVMHeader.toPrintMarkWord(a);
}
}.start();
}
}
分析运行结果,一个偏向锁如果进行一次其他线程的加锁,会膨胀为轻量级锁。
注意,这里的一个前提条件,第二次的加锁必须不能和第一次加锁存在竞争关系,否则就会直接膨胀为重量级锁。
仔细分析上面的代码,第二次的T1线程在进行加锁的时候, 主线程的 加锁代码块已经执行完毕,锁t1线程和主线程不存在竞争关系,所以这里的锁只会膨胀到轻量级锁。
如果两次加锁存在竞争关系,那么锁会膨胀到重量级锁,继续看下面的例子。
package com.com.kinyang.jol;
import org.openjdk.jol.info.ClassLayout;
import java.util.concurrent.TimeUnit;
/**
* @author KinYang Lau
* @date 2020/3/25 8:49 下午
* 偏向锁 ----> 重量级锁
*/
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Thread.sleep(5000);/// sleep 一段时间是为了让JVM激活偏向锁机制
ClassLayout classLayout = ClassLayout.parseInstance(Demo3.class);
A a = new A(); /// 现在new出来的对象默认是 可偏向的无锁状态 (101)
System.out.println("a 刚new出来:");
Print64JVMHeader.toPrintMarkWord(a);
Thread t1 = new Thread("T1"){
@Override
public void run() {
synchronized (a){
try {
System.out.println("\n 进入T1线程,对a进行synchronized加锁:");
Print64JVMHeader.toPrintMarkWord(a);
System.out.println("T1 进入睡眠,暂且不释放锁");
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t2 = new Thread("T2"){
@Override
public void run() {
synchronized (a){
try {
System.out.println("\n 进入T2线程,对a进行synchronized加锁:");
Print64JVMHeader.toPrintMarkWord(a);
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
System.out.println("让 t2 比 t1 晚启动2秒,造成t2线程与t1线程行程竞争关系");
t1.start();
TimeUnit.SECONDS.sleep(2);
t2.start();
TimeUnit.SECONDS.sleep(15);
}
}
运行结果:
分析这次结果,偏向锁如果出现一次线程直接的竞争,会直接膨胀为重量级锁。
总结一下:偏向锁 如果是在多个线程之间交替执行,不竞争,那么锁会膨胀为轻量级锁,如果存在线程之间竞争,那么会膨胀为重量级锁。
上面的几个案例,可以让直观的看到锁膨胀结果。但是无法看到锁膨胀的过程。
下面通过一个流程图进行锁膨胀的流程解析。