偏向锁的内存影响在于mark word 8字节内字段动态复用而非扩容:无锁时存哈希码,偏向时用线程id覆盖哈希码致其永久丢失;竞争触发撤销、升级及安全点停顿才是真实开销。

偏向锁的内存影响不在于“多占多少字节”,而在于它如何改变对象头中 Mark Word 的结构与语义,并在竞争发生时触发一系列不可逆的状态切换。理解这一点,才能真正看清锁升级背后的内存行为和运行时开销。
偏向锁对对象头的实际占用变化
在64位JVM中,Mark Word固定为8字节(64位),但内容布局随锁状态动态重排:
-
无锁状态:存储哈希码(31位)、GC分代年龄(4位)、偏向标志(0)、锁标志位(01)——此时可安全调用
hashCode() -
偏向锁状态:哈希码被挤掉,改存线程ID(54位)、epoch(2位)、年龄(4位)、偏向标志(1)、锁标志(01)——一旦写入线程ID,哈希码就永久丢失,后续再调用
hashCode()会强制撤销偏向并升级锁
注意:不是“多分配内存”,而是同一块8字节空间被复用,不同字段互斥存在。这种复用是JVM锁优化的关键设计,也是分析内存影响的起点。
变量竞争如何真实触发对象头状态切换
所谓“变量竞争”,本质是多个线程对同一对象执行 synchronized 块或方法。这种行为不会直接修改业务变量,但会持续读写对象头的Mark Word,从而驱动状态迁移:
- 线程A首次进入:JVM用CAS将自身ID写入Mark Word,标志位保持01、biased=1 → 进入偏向锁
- 线程B尝试进入:发现Mark Word中线程ID ≠ 自身ID,且biased=1 → 触发偏向撤销(需进入安全点暂停所有线程)
- 撤销后,对象头清空线程ID,恢复为无锁布局(但此时已无法再进偏向锁),随后立即膨胀为轻量级锁(标志位→00,存Lock Record指针)
整个过程不涉及业务变量本身,但每次竞争都导致Mark Word内容重写、栈帧新增Lock Record、甚至引发全局安全点停顿——这些才是真实的内存与性能代价。
用JOL工具实战验证对象头状态
借助 org.openjdk.jol:jol-core 可直接打印对象头二进制布局,观察状态切换:
- 新建对象未加锁:Mark Word末3位为
001(无锁不可偏向)或101(可偏向,取决于JVM启动参数) - 单线程执行一次synchronized:末3位变为
101,中间54位显示线程ID(需用十六进制解析) - 第二个线程争抢后再次打印:末3位变成
00(轻量级锁)或10(重量级锁),原线程ID字段被指针覆盖
关键提醒:调用 obj.hashCode() 或 System.identityHashCode(obj) 会提前固化哈希值到Mark Word,直接禁用偏向锁——这是生产环境中偏向锁“莫名失效”的最常见原因。
影响偏向锁生效的几个硬性条件
不是所有对象都能进偏向锁,JVM有明确限制:
- 默认延迟4秒启用偏向锁(可通过
-XX:BiasedLockingStartupDelay=0关闭) - 对象必须在延迟期后创建;启动即创建的对象无法获得偏向锁
- 类被显式禁用偏向(如
-XX:-UseBiasedLocking),或该类所有实例已发生过撤销 - 对象已被计算过identity hashcode——这是最易忽略的隐性干扰项
满足全部条件,才可能看到Mark Word中稳定写入线程ID;任一条件不满足,对象头将跳过偏向阶段,直接走轻量级锁路径。










