在说synchronized升级之前,先说明一下java对象的内存布局分为三部分:对象头、实例数据,对齐填充;其中对象头中含有对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志位、线程持有的锁、偏向线程ID等。在不同的锁状态标志位下有不同的差别,如下图所示:
如何查看对象占用内存的情况呢?OpenJDK 提供了一个非常好用的工具,JOL包,其maven依赖如下:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.13</version>
</dependency>
我们测试如下代码:
public static void main(String[] args) {
Head head = new Head();
System.out.println(ClassLayout.parseInstance(head).toPrintable());
}
// 定义一个类,用于获取对象的内存结构
static class Head {
}
输出结果如下图,其中锁状态标志位为01-无锁:
com.make.study.sync.JOLTest$Head 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)
// 对象实例总共占用16个字节
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
下面我们再来验证一下偏向锁,验证偏向锁需要增加一个jvm运行参数:
//关闭延迟开启偏向锁,jvm默认值是4000
-XX:BiasedLockingStartupDelay=0
偏向锁代码示例如下,启动一个线程,对代码块进行加锁,然后打印锁head对象:
static Head head = new Head();
public static void main(String[] args) throws Exception {
System.out.println(ClassLayout.parseInstance(head).toPrintable());
Thread t1 = new Thread(() -> {
testSync();
});
t1.start();
t1.join();
}
public static void testSync() {
synchronized (head) {
System.out.println(ClassLayout.parseInstance(head).toPrintable());
}
}
static class Head {}
输出结果如下,其中锁标志位01,是否偏向锁标志为1:
com.make.study.sync.SyncTest$Head 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) 96 ac 01 20 (10010110 10101100 00000001 00100000) (536980630)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
com.make.study.sync.SyncTest$Head object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 70 f9 1b (00000101 01110000 11111001 00011011) (469331973)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 96 ac 01 20 (10010110 10101100 00000001 00100000) (536980630)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
接下来我们再看看轻量级锁,看下面这个示例:
static Head head = new Head();
public static void main(String[] args) throws Exception {
// 执行代码之前先调用head的hashcode方法
System.out.println(Integer.toHexString(head.hashCode()));
System.out.println(ClassLayout.parseInstance(head).toPrintable());
Thread t1 = new Thread(() -> {
testSync();
});
t1.start();
t1.join();
}
public static void testSync() {
synchronized (head) {
System.out.println(ClassLayout.parseInstance(head).toPrintable());
}
}
static class Head {
}
从示例代码中可以看到,第一行先调用了head的hashCode方法,但是锁状态标志位并不是01,而是00-轻量级锁,因此我们得出结论,若先调用锁对应的对象的hashCode方法后,锁直接会升级为轻量级锁,结果如下:
// 这里打印的是head对象对应hashCode的16进制值
34340fab
com.make.study.sync.SyncTest$Head object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 ab 0f 34 (00000001 10101011 00001111 00110100) (873442049)
4 4 (object header) 34 00 00 00 (00110100 00000000 00000000 00000000) (52)
8 4 (object header) 96 ac 01 20 (10010110 10101100 00000001 00100000) (536980630)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
com.make.study.sync.SyncTest$Head object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) b8 ef 1f 1d (10111000 11101111 00011111 00011101) (488632248)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 96 ac 01 20 (10010110 10101100 00000001 00100000) (536980630)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
最后我们再来看看重量级锁的代码示例:
static Head head = new Head();
public static void main(String[] args) throws Exception {
System.out.println(ClassLayout.parseInstance(head).toPrintable());
Thread t1 = new Thread(() -> {
testSync();
});
Thread t2 = new Thread(() -> {
testSync();
});
t1.start();
t2.start();
t1.join();
t2.join();
}
public static void testSync() {
synchronized (head) {
System.out.println(ClassLayout.parseInstance(head).toPrintable());
}
}
static class Head {
}
输出结果如下,两个线程打印出的锁监视器对象的锁状态标志位为10,即重量级锁:
com.make.study.sync.SyncTest$Head 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) 96 ac 01 20 (10010110 10101100 00000001 00100000) (536980630)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
com.make.study.sync.SyncTest$Head object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) ea f7 61 1a (11101010 11110111 01100001 00011010) (442628074)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 96 ac 01 20 (10010110 10101100 00000001 00100000) (536980630)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
com.make.study.sync.SyncTest$Head object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) ea f7 61 1a (11101010 11110111 01100001 00011010) (442628074)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 96 ac 01 20 (10010110 10101100 00000001 00100000) (536980630)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
引用:
Java对象内存布局与访问定位