news 2026/9/29 15:59:54

Java 锁膨胀机制深度解析:从偏向锁到重量级锁的进化之路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java 锁膨胀机制深度解析:从偏向锁到重量级锁的进化之路

Java 锁膨胀机制深度解析:从偏向锁到重量级锁的进化之路

在 Java 并发编程中,synchronized关键字曾是性能低下的代名词。然而,从 JDK 1.6 开始,HotSpot 虚拟机对synchronized进行了大刀阔斧的优化,引入了偏向锁(Biased Locking)、**轻量级锁(Lightweight Locking)和重量级锁(Heavyweight Locking)**的分级机制。

这种机制的核心思想是:“根据竞争程度,动态调整锁的粒度”。在无竞争时尽量使用低成本操作,在有竞争时再升级为高成本但功能强大的锁。这一过程被称为锁膨胀(Lock Escalation)。

本文将深入剖析锁膨胀的全过程,揭示 JVM 如何在不同阶段进行极致优化。


一、基石:对象头(Object Header)与 Mark Word

要理解锁膨胀,首先必须理解 Java 对象在内存中的布局。每个 Java 对象在堆内存中都包含一个对象头(Object Header)。

在 64 位 JVM 开启压缩指针(默认开启)的情况下,对象头通常包含两部分:

  1. Mark Word(标记字):存储对象的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有记录等。它是锁升级的关键载体。
  2. Klass Pointer(类型指针):指向方法区中类的元数据。

Mark Word 的结构(64 位,压缩指针):

大小 (bit)内容说明
25Hash Code对象哈希值(未计算时为 0)
4GC 分代年龄对象在新生代存活的次数
1偏向锁标志0: 非偏向, 1: 偏向
2锁标志00: 无锁, 01: 偏向锁, 10: 轻量级锁, 11: 重量级锁
.........

关键点:锁的状态信息直接存储在对象头的 Mark Word 中。锁的升级过程,本质上就是修改 Mark Word 内容的过程。

注意:从 JDK 15 开始,偏向锁被标记为废弃(Deprecated),JDK 18 中默认禁用。但在理解 JVM 历史演进和底层原理时,偏向锁依然是不可或缺的一环。下文将以经典模型为主,并在最后说明新版本的变化。


二、第一阶段:偏向锁(Biased Locking)—— “假设没有竞争”

2.1 核心思想

场景:绝大多数锁在大部分时间内都是由同一个线程重复获取的(例如单线程执行同步代码块,或线程封闭的对象)。
优化策略:如果一个线程获得了锁,那么它就“偏向”于该线程。当该线程再次进入同步块时,无需进行任何原子操作(CAS),只需检查 Mark Word 中记录的线程 ID 是否为自己即可。

2.2 实现细节

  1. 初始状态:对象创建时,Mark Word 的锁标志位为01,偏向锁标志位为1(可偏向),但此时尚未绑定具体线程。
  2. 首次获取:
    • 线程 A 进入同步块。
    • JVM 发现是可偏向锁,通过CAS 操作将线程 A 的 ID 写入 Mark Word。
    • CAS 成功,线程 A 获得锁。
  3. 重入:
    • 线程 A 再次进入。
    • JVM 检查 Mark Word 中的线程 ID,发现就是自己。
    • 直接通过,无任何额外开销。
  4. 偏向撤销(Revoke):
    • 如果线程 B 试图获取该锁(发生竞争)。
    • JVM 会暂停拥有锁的线程 A(SafePoint),检查线程 A 是否还在使用该锁。
    • 若线程 A 已退出或未使用,直接将 Mark Word 重置为无锁状态或轻量级锁状态。
    • 若线程 A 仍在使用,则触发偏向锁撤销,升级为轻量级锁。

性能收益:在无竞争的单线程场景下,偏向锁消除了所有同步原语(CAS、互斥量)的开销,性能接近非同步代码。


三、第二阶段:轻量级锁(Lightweight Locking)—— “自旋避免阻塞”

3.1 核心思想

场景:存在轻微的竞争,但线程持有锁的时间很短。
优化策略:不使用操作系统层面的互斥量(Mutex),而是让用户态的线程通过**自旋(Spinning)**等待锁释放。因为线程切换(用户态->内核态)的开销远大于短时间自旋的 CPU 消耗。

3.2 实现细节:锁记录(Lock Record)

轻量级锁依赖栈帧中的锁记录(Displaced Mark Word)。

  1. 锁膨胀触发:
    • 偏向锁被撤销,或者对象一开始就不可偏向。
    • 线程尝试获取锁。
  2. 压栈:
    • JVM 在当前线程的栈帧中分配一块空间,称为锁记录。
    • 将对象头中的 Mark Word 复制一份到锁记录中(称为Displaced Mark Word)。
  3. CAS 替换:
    • 线程尝试使用CAS 操作,将对象头的 Mark Word 替换为指向当前栈帧中锁记录的指针。
    • 锁标志位变为00(轻量级锁)。
  4. 结果判断:
    • 成功:线程获得锁,执行同步代码。
    • 失败:说明有其他线程竞争。
      • 自旋:线程不会立即阻塞,而是循环检测对象头的 Mark Word 是否恢复(即锁是否被释放)。
      • 自适应自旋:JDK 1.6+ 引入了自适应自旋。自旋时间不再固定,而是根据上一次在同一把锁上的自旋时间及锁拥有者的状态动态调整。如果上次自旋成功了,这次就多转几圈;如果失败了,就少转几圈。
  5. 升级阈值:
    • 如果自旋超过一定次数(默认 10 次,可通过-XX:PreBlockSpin调整,新版由 JVM 自适应决定)仍未获取锁,或者竞争线程数超过 1 个(多于一人争抢),锁将膨胀为重量级锁。

性能收益:避免了用户态到内核态的切换,适合锁占用时间极短的场景。


四、第三阶段:重量级锁(Heavyweight Locking)—— “最后的防线”

4.1 核心思想

场景:竞争激烈,锁持有时间长,自旋浪费大量 CPU 资源。
优化策略:使用操作系统底层的互斥量(Mutex)。线程获取不到锁时,直接挂起(阻塞),进入等待队列,让出 CPU 给其他线程。

4.2 实现细节

  1. 锁膨胀:
    • 对象头的锁标志位变为10。
    • Mark Word 中存储指向ObjectMonitor(C++ 实现的监视器)的指针。
  2. 争夺过程:
    • 线程尝试获取锁失败后,被封装成ObjectWaiter节点,放入EntryList(等待队列)。
    • 线程状态从RUNNABLE变为BLOCKED,操作系统挂起该线程。
    • 当锁持有者释放锁时,JVM 会从 EntryList 中唤醒一个线程(或根据策略唤醒多个),使其重新竞争。
  3. 开销:
    • 涉及用户态与内核态的切换,上下文切换成本高。
    • 但保证了在高竞争下不会空转浪费 CPU。

五、锁的降级?不存在的!

这是一个常见的误区:锁只能升级,不能降级。

  • 升级路径:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。
  • 为何不降级?
    • 如果在运行过程中将重量级锁降级为轻量级锁,需要保证全局没有其他线程在竞争,这在多线程环境下极难安全地判断和维护。
    • 为了简化实现并保证安全性,一旦锁膨胀为重量级锁,直到对象被垃圾回收之前,它将一直保持重量级锁状态(即使后续没有竞争了)。

六、新版本的变化:偏向锁的退场

虽然上述“三级锁”模型是经典的面试考点和原理基础,但在实际生产环境的新版本 JDK 中,情况有所变化:

  1. JDK 15:偏向锁被标记为Deprecated。
  2. JDK 18:偏向锁默认禁用(可以通过-XX:+UseBiasedLocking强制开启,但不推荐)。

原因:

  • 现代应用架构变化:现代应用多为多线程并发,真正的“单线程重复获取锁”场景变少。
  • 撤销开销大:一旦发生竞争,偏向锁的撤销(Revoke)需要 Stop-The-World(STW),暂停所有线程来扫描栈帧,这个开销在某些高并发场景下反而比直接使用轻量级锁更大。
  • 简化逻辑:移除偏向锁可以简化 JVM 内部复杂的锁状态机逻辑。

现状:在现代 JDK 中,锁的升级路径通常简化为:无锁 -> 轻量级锁(自旋) -> 重量级锁。


七、总结与启示

JVM 的锁膨胀机制体现了计算机科学中经典的**“以空间换时间”和“分级处理”**思想:

锁状态适用场景核心优化手段代价
偏向锁单线程重入记录线程 ID,无 CAS,无同步开销撤销时需 STW
轻量级锁低竞争,短时持有CAS + 自旋(避免内核切换)占用栈空间,自旋耗 CPU
重量级锁高竞争,长时持有操作系统 Mutex,线程阻塞挂起用户态/内核态切换开销大

给开发者的建议:

  1. 减少锁粒度:尽量缩小同步代码块的范围,缩短持锁时间,让锁停留在轻量级阶段。
  2. 避免过度优化:不要手动强行指定锁类型,信任 JVM 的动态调整能力(除非你有极其特殊的性能分析数据)。
  3. 关注新版特性:如果使用 JDK 15+,不必再纠结偏向锁的调优,应更多关注CompletableFuture、StampedLock或ReentrantReadWriteLock等更高级的并发工具。
  4. 排查死锁:重量级锁一旦形成等待队列,极易引发死锁,务必使用jstack等工具定期监控。

理解锁膨胀机制,不仅是为了应对面试,更是为了在编写高并发代码时,心中有一幅清晰的“性能地图”,知道每一行synchronized背后,JVM 正在为你做什么。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/23 9:47:35

Ostrakon-VL-8B实战:基于YOLOv11的目标检测与视觉理解融合应用

Ostrakon-VL-8B实战:基于YOLOv11的目标检测与视觉理解融合应用 最近在尝试一些视觉大模型的应用,发现了一个挺有意思的组合:把最新的目标检测模型YOLOv11和视觉语言模型Ostrakon-VL-8B结合起来用。听起来可能有点技术,但实际效果…

作者头像 李华
网站建设 2026/8/23 9:47:37

新手必看:AgentCPM深度研报助手部署指南,从安装到生成全流程

新手必看:AgentCPM深度研报助手部署指南,从安装到生成全流程 你是不是经常需要写研究报告?不管是学生写论文、职场人做行业分析,还是研究者整理课题综述,最头疼的就是面对空白文档,不知道从何下手。查资料…

作者头像 李华
网站建设 2026/8/23 9:47:37

工业控制系统选型指南:PLC、DCS、FCS到底怎么选?(附场景对比表)

工业控制系统选型实战:PLC、DCS、FCS核心差异与场景化决策框架 在石化工厂的中控室里,李工程师盯着屏幕上跳动的压力曲线皱起了眉头——现有PLC系统对反应釜的连续控制已经力不从心,而隔壁车间的DCS系统预算又远超预期。这种场景每天都在全球…

作者头像 李华
网站建设 2026/8/23 9:47:49

WEBRTC DTLSv1.2 加密示例

1.1.计算共享密钥:from cryptography.hazmat.primitives.asymmetric import x25519 from cryptography.hazmat.backends import default_backend import binasciiprivate_key_bytes binascii.unhexlify("7f6fb1acf6fcee90c8090859a6c1a64d90d49b65002ccc880e5…

作者头像 李华
网站建设 2026/8/23 9:47:49

YOLO11检测中的模型A/B测试方法,讲解如何在线下对比多个模型的检测效果

🎬 Clf丶忆笙:个人主页 🔥 个人专栏:《YOLOv11全栈指南:从零基础到工业实战》 ⛺️ 努力不一定成功,但不努力一定不成功! 文章目录 一、为什么我们需要线下A/B测试:不仅仅是“看哪个更好” 1.1 从“感觉”到“数据”:决策的基石 1.2 线下A/B测试的核心价值:风险…

作者头像 李华
网站建设 2026/8/23 9:47:51

UniFormerV2实战:如何用ViT预训练模型快速搭建高效视频理解网络

UniFormerV2实战:基于ViT预训练模型的视频理解高效解决方案 视频理解技术正逐渐成为人工智能领域的热点研究方向,从智能监控到内容审核,从医疗影像分析到自动驾驶,这项技术正在重塑多个行业的运作方式。然而,传统视频处…

作者头像 李华