在说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对象内存布局与访问定位
