并发概念理解:https://blog.csdn.net/mashaokang1314/article/details/88750509
Java头的信息分析
这是一张hotspot源码中的注释
将这个图转成可读的表格
HotSpot官网对对象头的解释:
每个gc管理的堆对象开头的公共结构。(每个oop都指向一个对象头。)包括堆对象的布局、类型、GC状态、同步状态和标识哈希码的基本信息。由两个词组成。在数组中,它后面紧跟着一个长度字段。注意,Java对象和vm内部对象都有一个通用的对象头格式。
意思是java的对象头在对象的不同状态下会有不同的表现形式,主要有三种状态:无锁、加锁、gc标记状态,那么我们可以理解java当中的取锁其实是给对象上锁,也就是改变对象头的状态;如果上锁成功则进入同步代码块。但是java当中的锁分为很多种从图片上可以看出大体分为偏向锁、轻量级锁、重量级锁三种状态。这三种锁的效率完全不同。
java对象的布局以及对象头的布局
使用JOL来分析java对象布局
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
创建要测试的类
A.java
public class A {
}
JOLExample1.java
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import static java.lang.System.out;
public class JOLExample1 {
static A a=new A();
public static void main(String[] args) {
out.println(VM.current().details());
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
运行结果1
# Running 64-bit HotSpot VM.
# Using compressed oop with 0-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
分析结果1
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
分别对应:[Oop(Ordinary Object Pointer), boolean, byte, char, short, int, float, long, double]
大小.
整个对象一共16Byte,其中对象头12B,还有4个是对齐字节(因为在64为虚拟机上对象大小必须是8的倍数),如果定义的字段超出16B,则对象每次扩容8B,由于这个对象里面没有任何字段,故而对象的实例数据为0B?
什么叫做对象的实例数据?
对象头里面的12B到底存的是什么?
我们在A类里添加一个字段,再来查看运行结果:
public class A {
boolean flag=false;
}
运行结果2
# Running 64-bit HotSpot VM.
# Using compressed oop with 0-bit shift.
# Using compressed klass with 3-bit shift.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 1 boolean A.flag false
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
分析结果2
整个对象的大小还是16B,其中对象头12B、Boolean字段flag占1B、剩下的3B就是对齐字节。所以我们可以认为一个对象的布局大概分为三部分分别是:对象头、对象实例数据和对齐字节。
那么对象头为什么是12B,里面存储的是什么?(不同位数的VM对象头的长度不一样,这里是64bitVM)
关于java对象头的一些专业术语http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html
上面已经查看了对Object Header的解释
每个对象头的第一个单词。通常是一组位域,包括同步状态和标识哈希码。也可以是指向同步相关信息的指针(具有特征的低比特编码)。在GC期间,可能包含GC状态位。
每个对象头的第二个单词。指向描述原始对象的布局和行为的另一个对象(元对象)。对于Java对象,“klass”包含一个c++风格的“vtable”。
根据上述利用JOL打印的对象头信息可以知道一个对象头是12B,其中8B是mark word,那么剩下的4B就是klass word了,和锁相关的就是mark word了,那么接下来重点分析mark word里面的信息。
在无锁的情况下markword当中前56bit存的是对象的hashcode,那么来验证一下:
public class JOLExample1 {
static A a=new A();
public static void main(String[] args) {
out.println("before hash");
//没有计算hashcode之前的对象头
out.println(ClassLayout.parseInstance(a).toPrintable());
//JVM计算hashcode
out.println("jvm------0x"+Integer.toHexString(a.hashCode()));
//当计算完hashcode之后,我们可以查看对象头的变化
out.println("after hash");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
运行结果3
before hash
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 1 boolean A.flag false
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
jvm------0x6ae40994
after hash
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 94 09 e4 (00000001 10010100 00001001 11100100) (-469134335)
4 4 (object header) 6a 00 00 00 (01101010 00000000 00000000 00000000) (106)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 1 boolean A.flag false
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
可以看到计算hashcode之后的对象头中,由于是小端存储,所以mark word中从后往前数前7B也就是56bit存储的hashcode,
那么mark word中第一个字节存储的就是分代年龄、偏向锁信息、对象状态
关于对象状态一共分为5种状态,分别是无锁、偏向锁、轻量锁、重量锁、GC标记,但是对象状态只有两位如何表示5种状态,在jvm中对偏向锁和无锁都是00,不同的是偏向锁时,偏向位为1,无锁时偏向位为0,也就是101和001;
上面的对象信息都是无锁的,所以可以看出无锁的标志位为001;
偏向锁
public class JOLExample2 {
static A a;
public static void main(String[] args) {
a=new A();
out.println("before lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
//锁住a对象
synchronized (a){
System.out.println("hello");
}
out.println("after lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
运行结果4
before lock
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 1 boolean A.flag false
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
hello
after lock
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 1 boolean A.flag false
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
分析结果4
上面的这个程序只有一个主线程去锁住a,按道理来说应该是偏向锁,但是从打印信息来看锁住之后打印的还是00000001和无锁状态一样。这是因为虚拟机在启动的时候对于偏向锁有延迟,其实虚拟机在启动时除了运行你的程序,在这之前还有许多别的程序,比如gc,虚拟机知道这些程序中有同步方法,如果不停地加锁撤销锁这是很浪费性能的,所以默认情况下启动4秒之后,才开始加偏向锁,称为偏向锁延迟
可以让主线程睡眠4秒以上再看:
public class JOLExample2 {
static A a;
public static void main(String[] args) throws InterruptedException {
//执行线程睡眠4秒
Thread.sleep(4100);
a=new A();
out.println("before lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
//锁住a对象
synchronized (a){
System.out.println("hello");
}
out.println("after lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
运行结果
before lock
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 1 boolean A.flag false
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
hello
after lock
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 28 38 03 (00000101 00101000 00111000 00000011) (54011909)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 1 boolean A.flag false
13 3 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
可以看到mark word的第一个字节变成了00000101,发生了改变,我们也可以使用jvm参数- XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
来关闭偏向锁延迟;
设置取消偏向锁延迟并且取消主线程睡眠
运行结果
before lock
com.luban.B object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
hello
after lock
com.luban.B object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 28 13 03 (00000101 00101000 00010011 00000011) (51587077)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
为什么在没有对a进行同步时也就是before lock时就是101偏向锁?
其实这时候不能说是偏向锁,可以说是可偏向状态,因为偏向标志位是1,而对象头中并没有线程信息。
性能对比偏向锁和轻量级锁
public class A {
int i;
public synchronized void parse(){
i++;
}
}
public class JOLExample3 {
static A a;
public static void main(String[] args) throws InterruptedException {
a = new A();
long start=System.currentTimeMillis();
for (long i = 0; i < 1000000000L; i++) {
a.parse();
}
long end=System.currentTimeMillis();
System.out.println(String.format("%ms",end-start));
}
}
对于上面的代码,加上jvm关闭偏向锁延迟参数使用的就是偏向锁,如果不加就是轻量级锁;
运行结果:
偏向锁:
轻量级锁:
那么什么是轻量级锁?工作原理是什么,为什么比偏向锁慢,轻量级锁尝试在应用层面解决线程同步问题而不触发操作系统的互斥操作,轻量级锁减少多线程进入互斥的几率,不能代替互斥。
来看一下轻量级锁的对象头
public class JOLExample4 {
static B b;
public static void main(String[] args) throws InterruptedException {
b=new B();
out.println("before lock");
out.println(ClassLayout.parseInstance(b).toPrintable());
//锁住a对象
synchronized (b){
out.println("locking...");
out.println(ClassLayout.parseInstance(b).toPrintable());
}
out.println("after lock");
out.println(ClassLayout.parseInstance(b).toPrintable());
}
}
before lock
com.luban.B object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
locking...
com.luban.B object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 10 f5 e0 02 (00010000 11110101 11100000 00000010) (48297232)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
after lock
com.luban.B object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
在locking中对象状态为为00,这是也因为偏向锁延迟的原因导致,jvm在进行同步时加上了轻量级锁,所以这种情况下即使没有线程竞争,也是轻量级锁,故意我们认为轻量级锁的对象状态位为00;
性能对比轻量级和重量级
public class JOLExample5 {
static CountDownLatch countDownLatch=new CountDownLatch(1000000000);
public static void main(String[] args) throws InterruptedException {
final A a=new A();
long start=System.currentTimeMillis();
//开启两个线程执行,使产生竞争变为重量级锁
for (int i = 0; i < 2; i++) {
new Thread(){
@Override
public void run() {
while (countDownLatch.getCount()>0){
a.parse();
countDownLatch.countDown();
}
}
}.start();
}
countDownLatch.await();
long end=System.currentTimeMillis();
System.out.println(String.format("%sms",end-start));
}
}
查看重量级锁对象头
public class JOLExample6 {
static A a;
public static void main(String[] args) throws InterruptedException {
a=new A();
out.println("before lock");
out.println(ClassLayout.parseInstance(a).toPrintable()); //无锁
Thread t1 = new Thread(){
@Override
public void run() {
synchronized (a){ //t1获取到锁,睡眠
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
Thread.sleep(1000);
out.println("t1 locking");
out.println(ClassLayout.parseInstance(a).toPrintable()); //轻量级锁
sync();
out.println("after lock");
out.println(ClassLayout.parseInstance(a).toPrintable()); //锁释放,但是打印出来不一定能够使无锁
System.gc();
out.println("after gc");
out.println(ClassLayout.parseInstance(a).toPrintable()); //无锁
}
public static void sync(){
synchronized (a){
out.println("t1 main lock");
out.println(ClassLayout.parseInstance(a).toPrintable()); //重量级锁
}
}
}
before lock
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
t1 locking
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 68 f3 10 1a (01101000 11110011 00010000 00011010) (437318504)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
t1 main lock
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) ca c6 af 02 (11001010 11000110 10101111 00000010) (45074122)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
after lock
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) ca c6 af 02 (11001010 11000110 10101111 00000010) (45074122)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
after gc
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 09 00 00 00 (00001001 00000000 00000000 00000000) (9)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
重量级锁标志位为10;
如果调用wait方法则立刻变成重量锁
public class JOLExample7 {
static A a;
public static void main(String[] args) throws InterruptedException {
a=new A();
out.println("before lock");
out.println(ClassLayout.parseInstance(a).toPrintable()); //无锁
Thread t1 = new Thread(){
@Override
public void run() {
synchronized (a){
try {
synchronized (a){
System.out.println("before wait");
out.println(ClassLayout.parseInstance(a).toPrintable());
a.wait();
System.out.println("after wait");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
Thread.sleep(5000);
synchronized (a){
a.notifyAll();
}
}
}
注意如果对象已经计算了hashcode就不能使用偏向锁了
public class JOLExample2 {
static A a;
public static void main(String[] args) throws InterruptedException {
//执行线程睡眠4秒
Thread.sleep(4100);
a=new A();
a.hashCode(); //计算对象的hashcode
out.println("before lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
//锁住a对象
synchronized (a){
out.println("locking");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
out.println("after lock");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
运行结果
before lock
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 a5 e5 4a (00000001 10100101 11100101 01001010) (1256563969)
4 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
locking
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) c0 f7 ae 02 (11000000 11110111 10101110 00000010) (45021120)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
after lock
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 a5 e5 4a (00000001 10100101 11100101 01001010) (1256563969)
4 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
可以看到,计算了对象的hashcode之后,同步块内使用的是轻量级锁,而且出了同步快,轻量级锁会释放掉变为无锁。
没有计算hashcode
取消计算hashcode,运行结果
before lock
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
locking
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 28 f7 02 (00000101 00101000 11110111 00000010) (49752069)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
after lock
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 28 f7 02 (00000101 00101000 11110111 00000010) (49752069)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
偏向锁即使运行完同步代码也不会撤销,而是继续偏向原来的线程。
轻量级锁是什么时候发生的?
线程交替执行,无竞争时发生;
public class JOLExample8 {
static A a;
public static void main(String[] args) throws InterruptedException {
a=new A();
out.println("before lock");
out.println(ClassLayout.parseInstance(a).toPrintable()); //可偏向状态
Thread t1=new Thread(){
@Override
public void run() {
synchronized (a){
out.println("t1 lock ing");
out.println(ClassLayout.parseInstance(a).toPrintable()); //偏向锁
}
}
};
t1.start();
t1.join(); //让t1先执行完,就不会有竞争
out.println("t1 lock end");
out.println(ClassLayout.parseInstance(a).toPrintable()); //偏向锁
//主线程获取锁
synchronized (a){
out.println("main lock ing");
out.println(ClassLayout.parseInstance(a).toPrintable()); //轻量锁
}
out.println("main lock end");
out.println(ClassLayout.parseInstance(a).toPrintable()); //轻量锁撤销,还原为无锁
}
}
before lock
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
t1 lock ing
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b8 88 19 (00000101 10111000 10001000 00011001) (428390405)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
t1 lock end
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b8 88 19 (00000101 10111000 10001000 00011001) (428390405)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
main lock ing
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) d8 f1 5b 02 (11011000 11110001 01011011 00000010) (39580120)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
main lock end
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
分析结果
偏向锁在执行完同步代码块后并不会释放,而是继续偏向之前的线程;轻量锁在执行完同步代码块会释放,还原成无锁状态。
重量级锁什么时候发生?
存在线程之前的竞争;
还是上面的代码,取消t1.join();让两个线程竞争执行;
运行结果:
before lock
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
t1 lock end
t1 lock ing
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 f0 3e 1a (00000101 11110000 00111110 00011010) (440332293)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 f0 3e 1a (00000101 11110000 00111110 00011010) (440332293)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
main lock ing
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 8a b4 7e 03 (10001010 10110100 01111110 00000011) (58635402)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
main lock end
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 8a b4 7e 03 (10001010 10110100 01111110 00000011) (58635402)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
批量重偏向与批量撤销
从偏向锁的加锁和解锁过程中可以看出,当之后一个线程反复进入同步块时,偏向锁带来的性能开销基本可以忽略,但是当有其他线程尝试获取锁时,就需要等到safe point 时,再将偏下锁撤销为无锁或升级为轻量级,会消耗一定性能,所以在多线程竞争频繁的情况下,偏向锁不仅不能提高性能,反而会导致性能下降。于是就有了批量偏向和批量撤销的机制。
批量重偏向
public class JOLExample12 {
static List<A> list = new ArrayList<A>();
public static void main(String[] args) throws Exception {
Thread t1 = new Thread() {
public void run() {
for (int i=0;i<100;i++){
A a = new A();
synchronized (a){
System.out.println("111111");
list.add(a);
}
}
}
};
t1.start();
t1.join();
out.println("befre t2");
//偏向
out.println(ClassLayout.parseInstance(list.get(1)).toPrintable());
Thread t2 = new Thread() {
int k=0;
public void run() {
for(A a:list){
synchronized (a){
System.out.println("22222");
if (k==25){
out.println("t2 ing");
//轻量锁
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
k++;
}
}
};
t2.start();
}
}
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 38 3a 19 (00000101 00111000 00111010 00011001) (423245829)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 20 (00000101 11000010 00000000 00100000) (536920581)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
t2线程操作第5个对象
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 10 f0 b6 1a (00010000 11110000 10110110 00011010) (448196624)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 20 (00000101 11000010 00000000 00100000) (536920581)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
t2线程操作第25个
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 f1 2d 1a (00000101 11110001 00101101 00011010) (439218437)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 20 (00000101 11000010 00000000 00100000) (536920581)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
可以看到t1线程将所有a对象对象头都设成偏向锁;t1线程执行完,t2线程开始访问每个a对象,按道理应该是将所有a对象都设成轻量级锁,但是可以看到第5个是轻量级锁,而第25个是偏向锁;按理论偏向锁重偏向的阈值为20,也就是从第20个开始,jvm觉得偏向锁有问题,不再升级为轻量级锁,而是直接修改对象头信息为t2线程信息,也就是重偏向t2线程,这就是批量重偏向
结论:前19个对象偏向锁升级为轻量级锁,而且执行完同步代码块会释放锁;从第20个开始,直接偏向t2线程,也就是偏向t2的偏向锁,执行完同步快不会释放。
原理:以class为单位,为每一个class维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操作时,该计数器+1,当这个值达到重偏向阈值(20)时,JVM就认为该class的偏向锁有问题,因此会进行批量重偏向。
每个class对象会有一个对应的epoch字段,每个处于偏向锁状态对象的Mark Word中也有该字段,其初始值为创建该对象时class中的epoch值。
每次发生批量重偏向时(阈值达到20时),就将该值+1,同时遍历JVM中所有线程的栈,找到该class所有正处于加锁状态的偏向锁(同步代码块之外),将其epoch字段改为新值(正在处于同步块内的对象不会更新)。下次获得锁时,发现当前对象的epoch值和class的epoch不相等,那就算当前已经偏向了其他线程,也不会执行撤销操作,而是通过CAS操作将其Mark Word的Thread Id改为当前线程Id。
当达到重偏向阈值后,假如该class计数器继续增长,当期达到批量撤销阈值(40)后,JVM就认为该class的使用场景存在多线程竞争,会标记该class为不可偏向,之后,对于该class的锁,直接走轻量级锁的逻辑。
单个对象也会发生重偏向?
public class JOLExample10 {
static A a;
public static void main(String[] args) throws InterruptedException {
a=new A();
Thread t1 = new Thread(){
@Override
public void run() {
synchronized (a){
out.println("t1 locking");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
};
t1.start();
t1.join();
Thread t2 = new Thread(){
@Override
public void run() {
synchronized (a){
out.println("t2 locking");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
};
t2.start();
}
}
运行结果
t1 locking
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 20 cf 18 (00000101 00100000 11001111 00011000) (416227333)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 20 (00000101 11000010 00000000 00100000) (536920581)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
t2 locking
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 20 cf 18 (00000101 00100000 11001111 00011000) (416227333)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 20 (00000101 11000010 00000000 00100000) (536920581)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
分析结果
按道理来说t1执行完,a对象时偏向t1的,如果t2再来执行就升级为轻量锁;但是上面运行结果a对象却重偏向了t2,但是两次偏向的线程id确是一样的。
猜想:如果两个线程干的是一样的事,并且是交替执行无竞争的,那么两个线程会复用资源,t1执行完后,操作系统对t1的id进行了复用,使得t2的id和t1的id一样,但是synchronized却只认线程id,以为t2还是t1所以算不得真正意义上的重偏向。
如果在t1和t2之间再创建一个线程,使得t2不能复用t1的id
public class JOLExample10 {
static A a;
public static void main(String[] args) throws InterruptedException {
a=new A();
Thread t1 = new Thread(){
@Override
public void run() {
synchronized (a){
out.println("t1 locking");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
};
t1.start();
t1.join();
Thread tmp = new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
tmp.start();
Thread t2 = new Thread(){
@Override
public void run() {
synchronized (a){
out.println("t2 locking");
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
};
t2.start();
}
}
运行结果
t1 locking
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 a8 a8 18 (00000101 10101000 10101000 00011000) (413706245)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 61 c2 00 20 (01100001 11000010 00000000 00100000) (536920673)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
t2 locking
com.luban.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 40 f2 31 1a (01000000 11110010 00110001 00011010) (439480896)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 61 c2 00 20 (01100001 11000010 00000000 00100000) (536920673)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
可以看到t2成了轻量级锁,这证明猜想成立,即没有发生重偏向。
批量撤销
接批量重偏向的案例,如果t2 20之后发生批量重偏向,因为每次重偏向之前会先撤销t1的偏向锁再偏向t2,这时如果第三个线程t3也来了,t3也重偏向到20,即撤销了40(批量撤销阈值)次,这时便不会再重偏向而是直接升级为轻量。