如何通过分析 Synchronized 的锁膨胀机制理解从偏向锁到重量级锁的位状态迁移
如何通过分析 Synchronized 的锁膨胀机制理解从偏向锁到重量级锁的位状态迁移

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
简单来说,锁的状态就藏在对象头的标记位里:偏向锁的Mark Word低3位是101,轻量级锁是000,而重量级锁则是010。识别这些位模式,并理解它们之间不可逆的迁移路径,是掌握Synchronized底层机制的关键。当然,这个过程不能光靠理论推演,必须结合字节码分析、JOL工具输出和特定的JVM参数来交叉验证。
偏向锁的 Mark Word 位模式怎么识别
偏向锁的实现,绕开了操作系统的互斥量,其奥秘全在对象头那个小小的Mark Word里。具体来说,它依赖里面的线程ID和epoch字段来做标记。当一个对象尚未被任何锁定时,它的Mark Word低3位显示为001,代表无锁状态。一旦JVM启用了偏向锁优化,并且这个对象第一次被某个线程获取,JVM就会通过CAS操作,把当前线程的ID写入Mark Word的高位区域,同时把低3位从001改为101——这个101就是偏向锁独一无二的身份证。
不过,在实际观察时,有几个坑很容易踩到:
- 直接去读
Mark Word的十六进制值,却忽略了JVM在内存中的大端/小端布局,导致位解析完全错位。 - 在已经通过
-XX:-UseBiasedLocking参数全局禁用偏向锁的环境里,还试图寻找偏向锁的行为,自然是徒劳无功。 - 使用JOL(Ja va Object Layout)这个神器来查看对象布局时,如果没加上
-XX:BiasedLockingStartupDelay=0参数,那么JVM默认会有4秒的延迟,在这期间创建的对象是不会进入偏向状态的。
轻量级锁触发时 Mark Word 怎么变化
天下没有不散的筵席,偏向锁的“独占”状态也是。当另一个线程也来尝试获取这个已经被偏向的锁时,故事就进入了下一章。JVM会首先执行偏向锁撤销(revoke),然后尝试让竞争线程走轻量级锁的流程。这个过程的核心,依然是CAS:线程会试图将自己栈帧中的锁记录(Lock Record)地址写入对象的Mark Word。如果成功,Mark Word的低3位就会从101变成000,这个000正是轻量级锁的标志。此时,对象头里存储的不再是线程ID,而是一个指向线程栈空间的指针。
这里有几个关键细节值得玩味:
- CAS失败并不意味着锁会立刻膨胀升级。失败后,线程会进入自旋(默认10次,可通过
-XX:PreBlockSpin调整),在自旋期间,Mark Word依然保持着000的轻量级锁状态。 - 如果在自旋过程中,原来持有锁的线程恰好退出了同步块,锁会被释放,
Mark Word会恢复为无锁的001。这时,自旋的线程就有机会重新竞争,甚至可能再次让对象偏向自己。 - 只有当自旋耗尽,锁依然没能获取成功,才会真正触发锁膨胀。这时,
Mark Word的内容会被替换为指向一个重量级ObjectMonitor结构的指针,其低3位也随之变为010。
重量级锁的 010 标识为什么不可逆
当你在对象头里看到010这个三位标识时,就意味着对象已经进入了重量级锁的领域。此时,Mark Word里存储的是一个指向ObjectMonitor结构的指针。这个结构由JVM在堆上专门分配,并且与操作系统底层的mutex(互斥量)相关联,因此锁操作会涉及用户态到内核态的切换,开销自然就上去了。
那么,为什么说这个过程是不可逆的呢?锁一旦升级为重量级,就再也回不去了,这背后有几个根本原因:
ObjectMonitor结构一旦创建,其内部维护的队列(如_EntryList、_WaitSet)可能已经挂载了正在阻塞等待的线程。如果此时降级,这些线程的等待状态和唤醒语义将无法得到保证,会引发混乱。- 偏向锁和轻量级锁的生命周期与线程的栈帧紧密绑定。而重量级锁的
ObjectMonitor是独立于栈帧存在的堆对象,管理域完全不同,技术上无法安全地回退到依赖栈帧的轻量级状态。 - 从JVM源码的实现来看,锁膨胀(
inflate)是一个单向的构造过程,并没有设计对应的收索(deflate)逻辑来支持降级。
怎么验证锁状态迁移过程
理论说得再透彻,不如动手验证一遍。要真正看清锁状态的迁移,不能依赖猜测或片面的日志,必须进行交叉验证,主要从三个维度入手:字节码、对象头实际数据和JVM运行时参数。
- 首先,用
ja vap -v命令反编译你的Class文件,确认同步块是否确实生成了monitorenter和monitorexit指令。 - 接着,借助JOL工具,通过
ClassLayout.parseInstance(obj).toPrintable()实时打印出对象的布局,特别是Mark Word的十六进制值。然后,你需要对照JVM源码(比如markOop.hpp文件)中的位定义表,像解码密码一样解析出每一位的含义。 - 此外,可以加上
-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1这样的JVM参数,观察偏向锁撤销时是否会引发全局安全点(Safepoint),这对理解性能影响很有帮助。 - 最后,注意避开一些“陷阱”对象。比如容器类(
HashMap)或字符串常量池中的对象,它们内部可能默认禁用了偏向锁,在这上面测试会得不到预期的结果。
说到底,最容易忽略的一个核心点是:锁膨胀并非发生在你调用synchronized的那一刻,而是在后续线程**竞争获取锁失败时**才被触发。而且,所有的状态变更都是以单个对象为粒度的,这意味着同一个类的两个不同实例,完全可能处于截然不同的锁状态——一个可能还是无锁,另一个可能已经膨胀为重量级锁了。理解这一点,才能算真正摸清了Synchronized的脾气。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
Ubuntu系统Java路径怎么配置
在Ubuntu系统中配置Ja va路径 在Ubuntu系统里配置Ja va环境,其实是个挺常见的需求。这事儿说简单也简单,核心就两步:设置好JA VA_HOME环境变量,再把Ja va的可执行文件路径加到PATH里。下面咱们就一步步来,把这事儿彻底搞定。 第一步:安装Ja va 如果你系统里还没装J
Ubuntu中Java内存设置如何调整
在Ubuntu系统中调整Ja va内存设置 在Ubuntu系统上运行Ja va应用,内存配置是个绕不开的话题。调得好,应用跑得飞快;调得不对,性能瓶颈甚至崩溃都可能找上门。好在调整方法并不复杂,关键得找准场景。下面这张图,可以帮你快速建立起一个直观的印象: 接下来,咱们就聊聊几种主流的调整路径,你可
Java程序在Ubuntu上运行慢怎么办
Ja va程序在Ubuntu上运行慢怎么办 遇到Ja va程序在Ubuntu上性能不佳的情况,确实让人头疼。不过别担心,这通常不是无解的问题。性能瓶颈往往出在几个关键环节,只要方法得当,完全有希望让程序“跑”得更顺畅。下面,我们就来系统地梳理一下那些行之有效的优化思路。 1 优化Ja va虚拟机(
Java服务在Ubuntu如何备份
在Ubuntu上备份Ja va服务,通常涉及以下几个步骤 为Ja va服务建立一套可靠的备份机制,是保障业务连续性的基础。这个过程环环相扣,从停止服务到最终的安全存储,每一步都至关重要。下面,我们就来详细拆解这个标准操作流程。 1 停止Ja va服务 备份的第一步,是确保数据的一致性。想象一下,如
Ubuntu下Java内存如何配置
在Ubuntu下配置Ja va内存,通常需要修改Ja va应用程序的启动脚本或使用命令行参数来设置Ja va虚拟机(JVM)的内存参数。以下是一些常见的方法: 方法一:修改启动脚本 这个方法最直接,适用于那些通过特定脚本启动的应用。具体操作分三步走: 定位启动脚本:首先得找到负责启动Ja va应用的
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
相关攻略
2015-03-10 11:25
2015-03-10 11:05
2021-08-04 13:30
2015-03-10 11:22
2015-03-10 12:39
2022-05-16 18:57
2025-05-23 13:43
2025-05-23 14:01
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

