对象头里偏向锁说明
约 1372 字大约 5 分钟
2026-02-25
总结:偏向锁是特定历史阶段的性能优化手段,在单线程时代有效,但在现代高并发环境中已逐渐退出舞台。理解其原理有助于深入掌握 JVM 并发机制,但在新项目中通常无需主动启用。
一、概述
偏向锁(Biased Locking) 是 HotSpot JVM 为优化 单线程重复进入同步块 场景而引入的一种锁优化机制。其核心思想是:
“如果一个锁总是被同一个线程获取,就将该锁‘偏向’于该线程,后续进入同步块时无需任何同步操作。”
通过减少不必要的 CAS 和原子操作,显著提升低竞争或单线程场景下的 synchronized 性能。
二、设计目标
- ✅ 消除单线程反复加锁/解锁的开销。
- ✅ 避免轻量级锁中频繁的 CAS 操作。
- ✅ 在无竞争场景下实现“零成本同步”。
三、工作原理
3.1 对象头与 Mark Word
每个 Java 对象在内存中包含一个 对象头(Object Header),其中 Mark Word 存储运行时元数据,包括锁状态。
在 64 位 JVM 中,偏向锁状态下的 Mark Word 布局如下:
| 字段 | 位数 | 说明 |
|---|---|---|
| JavaThread* | 54 位(含 unused) | 持有偏向锁的线程 ID |
| epoch | 2 位 | 用于批量撤销(Bulk Revocation) |
| age | 4 位 | GC 分代年龄 |
| biased_lock | 1 位 | 是否启用偏向(1 = 是) |
| lock | 2 位 | 锁标志位(01 表示无锁或偏向锁) |
示例值(十六进制):
0x0000000100000005(偏向线程 ID 为 5)
3.2 加锁流程
- 初始状态:对象处于 可偏向但未锁定 状态(biased_lock=1, thread=0)。
- 线程 A 首次加锁:
- JVM 检测到对象支持偏向锁。
- 通过 CAS 将线程 A 的 ID 写入 Mark Word。
- 成功 → 进入 偏向锁状态。
- 线程 A 再次加锁:
- 直接比对 Mark Word 中的线程 ID。
- 若匹配 → 无需任何同步操作,直接执行临界区代码。
- 其他线程(如 B)尝试加锁:
- 发现偏向线程 ≠ 自己。
- 触发 偏向锁撤销(Bias Revocation)。
- 锁升级为 轻量级锁 或 重量级锁。
3.3 解锁流程
- 偏向锁 没有显式的解锁操作。
- 线程退出同步块时,不做任何事情(因为锁仍偏向它,下次可直接进入)。
- 只有当其他线程竞争时,才触发撤销。
四、偏向锁撤销(Bias Revocation)
4.1 何时触发?
- 任意其他线程尝试获取已被偏向的锁。
- 调用
System.identityHashCode()(因 hashCode 会占用 Mark Word,与偏向信息冲突)。 - 对象进入同步队列(如作为
wait()的 monitor)。
4.2 撤销过程
- JVM 请求一个 安全点(Safepoint)。
- 暂停所有线程(STW)。
- 检查持有偏向锁的线程状态:
- 若已退出同步块 → 直接撤销,对象变为普通无锁状态。
- 若仍在同步块中 → 撤销失败,锁膨胀为 重量级锁。
- 对象标记为 不可再偏向(后续走轻量/重量路径)。
⚠️ 撤销代价高:涉及 STW,可能影响应用延迟。
五、批量重偏向与批量撤销(Bulk Rebias / Revocation)
为避免频繁单个撤销,JVM 引入 epoch 机制:
- 每个类维护一个 epoch 值。
- 当某类的对象被大量撤销时,JVM 可:
- 批量重偏向:将同一类的所有对象偏向新线程(若原线程不再使用)。
- 批量撤销:一次性撤销该类所有对象的偏向锁。
通过 -XX:BiasedLockingBulkRebiasThreshold 和 -XX:BiasedLockingBulkRevocationThreshold 控制阈值(默认 20 / 40)。
六、配置参数
| 参数 | 默认值 | 说明 |
|---|---|---|
-XX:+UseBiasedLocking | JDK 14 及以前:true JDK 15+:false | 启用偏向锁 |
-XX:BiasedLockingStartupDelay=4000 | 4000 ms | JVM 启动后延迟启用偏向锁(避免启动期竞争) |
-XX:-BiasedLockingStartupDelay | — | 设为 0 可立即启用 |
-XX:BiasedLockingBulkRebiasThreshold | 20 | 批量重偏向阈值 |
-XX:BiasedLockingBulkRevocationThreshold | 40 | 批量撤销阈值 |
建议:在 JDK 8/11 中若确认为单线程应用,可关闭延迟:
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
七、优缺点分析
✅ 优点
- 单线程同步性能接近无锁。
- 减少 CAS 和内存屏障开销。
- 适合内部状态对象、日志器等低竞争场景。
❌ 缺点
- 撤销成本高(STW)。
- 不适用于高并发环境(现代应用多为多线程)。
- 与
hashCode()冲突(调用后立即撤销偏向锁)。 - 增加 JVM 实现复杂度。
八、现状与演进
| JDK 版本 | 偏向锁状态 |
|---|---|
| JDK 6 ~ 14 | 默认启用(有 4 秒延迟) |
| JDK 15 | 默认禁用(JEP 374) |
| JDK 17+ | 已废弃(deprecated),未来可能移除 |
原因:现代应用并发度高,偏向锁收益远小于其维护和撤销成本。
九、最佳实践建议
| 场景 | 建议 |
|---|---|
| JDK 8 / 11,单线程或极低并发 | 可保留偏向锁,或关闭启动延迟 |
| JDK 15+ | 无需关注,默认已禁用 |
高频调用 hashCode() 的对象 | 避免依赖偏向锁(因会立即失效) |
| 低延迟系统(如金融交易) | 显式禁用:-XX:-UseBiasedLocking,避免 STW 风险 |
| 不确定场景 | 保持默认即可,JVM 会自动适应 |
十、常见误区澄清
| 误区 | 正确理解 |
|---|---|
| “线程B获取失败,偏向锁保留” | ❌ 只要有竞争尝试,立即撤销 |
| “偏向锁是自旋锁” | ❌ 它完全无自旋,纯标识判断 |
| “偏向锁支持多线程交替访问” | ❌ 仅支持单一线程反复进入 |
| “调用 wait() 仍可用偏向锁” | ❌ wait/notify 必须使用 ObjectMonitor,会强制膨胀 |
