synchronized内存布局图(bit 精确位置)
以64位 HotSpot JDK 8下的对象头精确 bit 位布局图和反编译验证示例为例
一、64位 JDK 8 对象头(Mark Word)精确 bit 位布局
以下基于HotSpot 64位 VM,默认开启指针压缩(
-XX:+UseCompressedOops和-XX:+UseCompressedClassPointers),此时 Mark Word 占8 字节,Klass Pointer 占4 字节,对象头共 12 字节。
Mark Word 各状态 bit 位拆解(低位在右)
1. 无锁状态(biased_lock=0,lock=01)
|--------------------------------------------------| | unused:25 | identity_hashcode:31 | age:4 | 0 | 01 | |--------------------------------------------------| bits 63-39 bits 38-8 bits 7-4 3 2-0- bit 0–1:锁标志位
01(无锁/偏向未生效) - bit 2:偏向锁标记
0(无锁) - bit 3–6:分代年龄(age)
- bit 7–38:对象哈希码(如果已调用
System.identityHashCode()) - bit 39–63:未使用(或存 GC 元信息)
2. 偏向锁状态(biased_lock=1,lock=01)
|--------------------------------------------------------------| | thread:54 | epoch:2 | unused:1 | age:4 | 1 | 01 | |--------------------------------------------------------------| bits 63-10 bits 9-8 bit 7 bits 6-3 2 1-0- bit 0–1:锁标志位
01 - bit 2:偏向锁标记
1(表示是偏向锁状态) - bit 3–6:分代年龄(age)
- bit 7:未使用
- bit 8–9:偏向锁 epoch(用于批量重偏向)
- bit 10–63:持有偏向锁的线程 ID(54 bits)
3. 轻量级锁状态(lock=00)
|--------------------------------------------------| | 指向 Lock Record 的指针 | 00 | |--------------------------------------------------| bits 63-2 1-0- bit 0–1:
00 - bit 2–63:指向当前线程栈中Lock Record的指针(62 bits 可用,因为指针最后2位为0,实际存储时右移2位)
4. 重量级锁状态(lock=10)
|--------------------------------------------------| | 指向 ObjectMonitor 的指针 | 10 | |--------------------------------------------------| bits 63-2 1-0- bit 0–1:
10 - bit 2–63:指向 C++ 对象
ObjectMonitor的指针
5. GC 标记(lock=11)
|--------------------------------------------------| | GC 标记信息 | 11 | |--------------------------------------------------| bits 63-2 1-0- bit 0–1:
11(表示对象被 GC 标记,具体含义取决于 GC 算法)
图示总览(一个容易记忆的图表)
┌─────────────────────────────────────────────────────────────┐ │ Mark Word (64 bits) │ ├──────────────┬─────────────────────┬──────────┬─────────────┤ │ 无锁(001) │ unused:25 | hash:31 │ age:4 │ 0 │ 01 │ ├──────────────┼─────────────────────┼──────────┼─────────────┤ │ 偏向(101) │ thread:54 | ep:2 │ age:4 │ 1 │ 01 │ ├──────────────┼─────────────────────┼──────────┼─────────────┤ │ 轻量级(00) │ ptr to LockRecord (62 bits) │ 00 │ ├──────────────┼─────────────────────┼──────────┼─────────────┤ │ 重量级(10) │ ptr to ObjectMonitor (62 bits)│ 10 │ ├──────────────┼─────────────────────┼──────────┼─────────────┤ │ GC标记(11) │ GC info (62 bits) │ 11 │ └──────────────┴─────────────────────┴──────────┴─────────────┘其中age(4 bits)表示对象在年轻代中存活次数,达到阈值 15 将晋升到老年代。
二、反编译验证示例(javap 查看 monitorenter/monitorexit)
1. 编写简单同步代码
publicclassSynchronizedDemo{privatestaticfinalObjectlock=newObject();publicstaticvoidmain(String[]args){synchronized(lock){System.out.println("Hello synchronized");}}}2. 编译并反编译
javac SynchronizedDemo.java javap-c-vSynchronizedDemo.class3. 关键字节码输出(截取 main 方法)
publicstaticvoidmain(java.lang.String[]);Code:0:getstatic #2// lock3:dup4:astore_15:monitorenter// <-- 进入 synchronized6:getstatic #3// System.out9:ldc #4// "Hello synchronized"11:invokevirtual #5// println14:aload_115:monitorexit// <-- 退出 synchronized16:goto2419:astore_220:aload_121:monitorexit// <-- 异常时的退出22:aload_223:athrow24:returnExceptiontable:fromtotargettype61619anymonitorenter和monitorexit指令是 synchronized 的字节码基础。- 实际锁升级、对象头修改由 JVM 内部在运行时完成,字节码层面看不到锁状态。
三、JOL(Java Object Layout)验证对象头变化
JOL 是 OpenJDK 提供的工具,可以直接打印对象内存布局,直观看到 Mark Word 的变化。
1. Maven 依赖
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.17</version></dependency>2. 验证无锁 → 偏向锁 → 轻量锁 → 重量锁
importorg.openjdk.jol.info.ClassLayout;importorg.openjdk.jol.vm.VM;publicclassJolExample{publicstaticvoidmain(String[]args)throwsInterruptedException{// 等待偏向锁延迟过去(默认4秒)Thread.sleep(5000);Objectobj=newObject();// 无锁状态(实际是偏向未锁定,但JOL会显示为偏向锁不可用?)System.out.println("=== 刚创建,无锁 ===");System.out.println(ClassLayout.parseInstance(obj).toPrintable());// 加偏向锁(一个线程加锁)synchronized(obj){System.out.println("\n=== 偏向锁 ===");System.out.println(ClassLayout.parseInstance(obj).toPrintable());}// 另一个线程来竞争,导致升级为轻量锁(需要另外的线程,简单场景可用两个线程演示)Threadt=newThread(()->{synchronized(obj){System.out.println("\n=== 其他线程获得锁(轻量锁/重量锁)===");System.out.println(ClassLayout.parseInstance(obj).toPrintable());}});t.start();t.join();// 再main线程加锁,可能升级为重量锁(多竞争几次)for(inti=0;i<10;i++){newThread(()->{synchronized(obj){try{Thread.sleep(10);}catch(InterruptedExceptione){}}}).start();}Thread.sleep(100);synchronized(obj){System.out.println("\n=== 高竞争后,可能变为重量锁 ===");System.out.println(ClassLayout.parseInstance(obj).toPrintable());}}}3. 输出示例(节选关键字段)
=== 刚创建,无锁 === # 运行状态(无锁): OFF SZ TYPE DESCRIPTION 0 8 (object header) 01 00 00 00 00 00 00 00 (未偏向) 8 4 (class pointer) 12 4 (padding) === 偏向锁 === # 偏向状态: 0 8 (object header) 05 30 f2 4a 19 02 00 00 (末尾 05 表示偏向锁) === 其他线程获得锁(轻量锁)=== # 轻量锁状态(末尾 00 或 f0): 0 8 (object header) a8 f4 1f 78 71 7f 00 00 (末尾 bit 为 00) === 高竞争后,重量锁 === # 重量锁状态(末尾 3a 或 ba): 0 8 (object header) 3a 0a 56 1a 00 00 00 00 (末尾 10)注意:实际十六进制数据中,最后两个 bit(低位)是锁标志:
01且第3 bit 为 0 → 无锁05(00000101) → 偏向锁(101)00或f0等,最后两位 00 → 轻量锁3a(00111010)最后两位10→ 重量锁
四、如何确定当前 JVM 使用的是哪种锁?
可以通过打印系统属性或者 JVM 参数查看:
java-XX:+PrintFlagsFinal|grepBiasedLocking结果示例:
bool UseBiasedLocking=true{product}intx BiasedLockingStartupDelay=4000{product}若偏移锁关闭,则对象创建后直接为轻量级锁的“无锁可偏向”状态,实际不太一样,但底层仍会经历 “无锁 → 轻量锁 → 重量锁”。
五、小结
- 精确 bit 图:上面已给出 64 位下每个状态的使用位。
- 验证方法:
- 字节码:
javap看到monitorenter/exit - 内存布局:JOL打印对象头十六进制,对照锁标志位判断当前锁状态。
- 字节码:
