轻量级锁到重量级锁源码剖析前言轻量级锁到重量级锁源码剖析一、 锁膨胀的核心诱因与总体演进图景二、 核心源码剖析与极致注释说明1. 状态判定的源头markOop.hpp2. 锁膨胀终极无锁状态机synchronizer.cpp3. 重量级锁的入场争夺战objectMonitor.cpp三、 系统视角全时序演进链路梳理锁膨胀流程核心要点总结设计精妙点提炼前言本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限文中内容难免存在疏漏恳请读者不吝指正轻量级锁到重量级锁源码剖析当 Java 中的轻量级锁Lightweight Lock遭遇真正的多线程并发竞争或者由于持有锁的线程长期占用锁导致尝试获取锁的线程自旋失败时JVM 就会触发最终的同步跃迁——向重量级锁Heavyweight Lock膨胀。从轻量级锁到重量级锁的蜕变是 HotSpot 虚拟机中最核心、涉及多线程并发无锁状态机Lock-Free State Machine最复杂的逻辑。这一过程伴随着操作系统内核态与用户态的切换、线程挂起队列的构建以及底层互斥量Mutex的激活。一、 锁膨胀的核心诱因与总体演进图景轻量级锁的核心假设是多线程交替执行不存在实质性竞争。它的本质是将对象头Mark Word通过 CAS 交换到当前线程的栈帧Stack Frame中。一旦另一个线程尝试获取该锁并发现对象头已经指向了别人的栈帧且通过一定程度的自适应自旋仍未获锁轻量级锁的闭环即宣告破裂。此时JVM 必须打破基于线程栈的局部锁记录Lock Record在堆外Native Memory分配一个名为ObjectMonitor的重量级监视器并将对象头的指针彻底重定向到这个全局唯一的监视器上。以下是这一跃迁过程中涉及的核心 OpenJDK 8源码文件hotspot/src/share/vm/oops/markOop.hpp定义对象头的膨胀中哨兵态INFLATING及重量级标记。hotspot/src/share/vm/runtime/synchronizer.cpp锁膨胀的核心状态机实现ObjectSynchronizer::inflate。hotspot/src/share/vm/runtime/objectMonitor.cpp重量级锁的抢占、自适应自旋及线程挂起ObjectMonitor::enter。二、 核心源码剖析与极致注释说明1. 状态判定的源头markOop.hpp在向重量级锁演进时对象头会经历一个特殊的临界状态——“膨胀中”INFLATING。// 文件物理路径: hotspot/src/share/vm/oops/markOop.hppclassmarkOopDesc:publicoopDesc{public:enum{locked_value0,// 00: 轻量级锁unlocked_value1,// 01: 普通无锁monitor_value2,// 10: 重量级锁 (ObjectMonitor*)marked_value3,// 11: GC 标记biased_lock_pattern5// 101: 偏向锁};// 提供一个特殊的、全零的哨兵指针用来代表当前锁正处于“膨胀中”的过渡状态staticmarkOopINFLATING(){return(markOop)intptr_t(0);}// 判定当前 Mark Word 是否已经是指向 ObjectMonitor 的重量级锁boolhas_monitor()const{return((value()monitor_value)!0);// 检查低两位是否为 10}// 提取重量级锁中封装的 ObjectMonitor 指针ObjectMonitor*monitor()const{assert(has_monitor(),check);// 强制清除低两位的 10 标记还原出 ObjectMonitor 在堆外内存中的真实 64 位原生绝对地址return(ObjectMonitor*)(value()^monitor_value);}// 判定当前是否是轻量级锁即 Mark Word 是否指向某个线程的方法栈帧内部boolhas_locker()const{return((value()lock_mask_in_place)locked_value);// 检查低两位是否为 00}// 提取轻量级锁对应的栈内锁记录指针 (BasicLock*)BasicLock*locker()const{assert(has_locker(),check);return(BasicLock*)value();// 00 状态下Mark Word 的值直接就是栈地址指针}};2. 锁膨胀终极无锁状态机synchronizer.cpp当线程在slow_enter中发现 CAS 压入栈锁记录失败它会立刻调用ObjectSynchronizer::inflate。由于可能有成百上千个线程同时发现失败并试图对同一个对象实施锁升级inflate内部使用了一个基于死循环Loop-Free / CAS的并发状态机。// 文件物理路径: hotspot/src/share/vm/runtime/synchronizer.cppObjectMonitor*ObjectSynchronizer::inflate(Thread*Self,oop object){// 强制拉高 CPU 缓存一致性可见性OrderAccess::fence();for(;;){constmarkOop markobject-mark();assert(!mark-has_bias_pattern(),偏向锁必须在此前已被完全撤销);// // CASE 1: 已经是重量级锁状态 (10)// if(mark-has_monitor()){ObjectMonitor*infmark-monitor();assert(inf-header()-is_neutral(),监视器内部保存的初始头必须是无锁状态);assert(inf-object()object,监视器关联的对象一致性校验);returninf;// 快捷路径别的线程已经完成了膨胀直接返回现成的监视器}// // CASE 2: 其它线程正在执行膨胀 (INFLATING 哨兵态)// if(markmarkOopDesc::INFLATING()){// 极其精妙的防御性设计发现有同行在抢先扩建当前线程不能横加干涉// ReadStableMark 内部会通过短暂的自旋或 yield 让出 CPU 周期等待那个正在膨胀的线程完工ReadStableMark(object);continue;// 再次循环届时将步入 CASE 1}// // CASE 3: 核心升级路径 —— 当前对象正处于轻量级锁状态 (00)// if(mark-has_locker()){// 1. 从当前线程的私有内存块ObjectMonitor Allocator或全局空闲列表里分配一个干净的 ObjectMonitorObjectMonitor*momAlloc(Self);m-Recycle();m-_ResponsibleNULL;m-_recursions0;m-_SpinDurationObjectMonitor::Knob_SpinLimit;// 初始化自适应自旋阈值// 2. 关键防御通过 CAS 尝试将对象头占位符变更为 INFLATING (0x00000000)// 这是多线程抢夺“膨胀实施权”的唯一分水岭markOop cmp(markOop)Atomic::cmpxchg_ptr(markOopDesc::INFLATING(),object-mark_addr(),mark);if(cmp!mark){omRelease(Self,m,true);// CAS 失败说明别的线程抢先改变了锁状态释放刚申请的 Monitor 并重试continue;}// 3. 【独占控制阶段】当前线程成功拿到了膨胀特权开始“拆迁”轻量级锁// 提取原轻量级锁持有者Lock Owner残留在其栈帧锁记录Lock Record中的 displaced mark wordmarkOop dmwmark-displaced_mark_helper();assert(dmw-is_neutral(),对应的 displaced mark 必须是无锁干净的);// 4. 将原锁持有者的各项数据转移、缝合到 ObjectMonitor 结构体中m-set_header(dmw);// 注入最原始的无锁 Mark Word内含 Identity HashCode、GC年龄m-set_owner(mark-locker());// 重量级锁的所有权直接顺延分配给原轻量级锁的所有者即那个栈指针m-set_object(object);// 反向关联对象实例// 5. 决定性发布将对象头正式改写为指向该 ObjectMonitor 的绝对内存地址并在低两位打上 10 的重量级标志// release_set_mark 带有内存屏障确保上述对 m 的赋值在对外可见前全部落盘object-release_set_mark(markOopDesc::encode(m));// 6. 提示此时原轻量级锁拥有者线程还在愉快地运行它根本不知道自己的锁已经被“偷偷”升级了。// 当它未来执行 monitorexit 时会发现栈帧指针失效届时它会顺着对象头找到这个 ObjectMonitor 并负责将其解开。returnm;}// // CASE 4: 普通无锁状态 (01)// // 这种情况较为罕见通常发生于轻量级锁刚好在这一瞬间被原持有者退出了或者是由于调用了// Object.hashCode() 导致无偏向空间必须直接膨胀为重量级锁来存放 HashCode。assert(mark-is_neutral(),invariant);ObjectMonitor*momAlloc(Self);m-Recycle();m-set_header(mark);m-set_owner(NULL);// 此时没有任何人占有这把锁m-set_object(object);if(Atomic::cmpxchg_ptr(markOopDesc::encode(m),object-mark_addr(),mark)!mark){m-Recycle();omRelease(Self,m,true);continue;// CAS 失败则退回重试}returnm;}}3. 重量级锁的入场争夺战objectMonitor.cpp一旦inflate返回了ObjectMonitor*当前抢锁线程就会调用ObjectMonitor::enter。// 文件物理路径: hotspot/src/share/vm/runtime/objectMonitor.cppvoidObjectMonitor::enter(TRAPS){Thread*SelfTHREAD;// 1. 尝试一次快速的 CAS 抢锁将 _owner 从 NULL 变更为当前线程指针void*curAtomic::cmpxchg_ptr(Self,_owner,NULL);if(curNULL){return;// 抢锁成功直接返回进入临界区}// 2. 如果锁已被占检查是不是当前线程自己重入if(curSelf){_recursions;// 重入计数器加一return;}// 3. 检查当前锁的拥有者是不是原轻量级锁拥有者的“栈地址”// 如果是说明当前线程就是那个“原轻量级锁持有者”它是第一次步入重量级锁的判罚场if(Self-is_lock_owned((address)cur)){assert(_recursions0,internal invariant);_recursions1;_ownerSelf;// 成功将所有权从“栈指针”肉身替换为真正的“线程对象指针”return;}// // 进入真正的竞争深水区// Self-set_current_pending_monitor(this);// 4. 激活自适应自旋优化 (Adaptive Spinning)// 在决定挂起自身前先利用短时间的 CPU 轮询进行最后的挣扎试图等待 _owner 释放if(Knob_SpinEarly0TrySpin(Self)0){assert(_ownerSelf,invariant);Self-set_current_pending_monitor(NULL);return;// 自旋期间原锁持有者释放了锁当前线程幸运地截获了重量级锁}// 5. 自旋依然失败必须进入阻塞状态{// 将当前线程封装为一个 ObjectWaiter 节点ObjectWaiternode(Self);Self-_ParkEvent-Reset();node._notified0;node.TStateObjectWaiter::TS_CXQ;// 标记状态为准备进入多线程竞争队列// 通过无锁原子 CAS 操作将当前线程节点推入到 ObjectMonitor 的 _cxq (Contention Queue) 队列头部ObjectWaiter*nxt;for(;;){node._nextnxt_cxq;if(Atomic::cmpxchg_ptr(node,_cxq,nxt)nxt)break;}// 6. 调用操作系统底层的同步原语挂起线程for(;;){if(TryLock(Self)0)break;// 在挂起的最后一刻再次尝试肉搏抢锁// 步入真正的线程挂起内核态睡眠状态// os::park 内部在 Linux 平台下由 pthread_cond_wait 或更为底层的 sys_futex 系统调用实现if(_owner!Self){Self-_ParkEvent-park();}// 线程被原持有人在退出monitorexit时唤醒unpark后将从此处苏醒并重新进入 for 循环参与下一次抢锁}// 抢锁成功将自己从等待状态中清除Self-set_current_pending_monitor(NULL);}}三、 系统视角全时序演进链路梳理为了更清晰地理解这一复杂的过程我们将轻量级锁到重量级锁的演进划分为以下四个步骤[线程 B (竞争者)] [对象 obj 头 (Mark Word)] [线程 A (原轻量锁持有者)] | | | |-- 1. CAS 压栈失败 ---------------| | | | | |-- 2. 独占执行 inflate() ---------| | | a. CAS 写入 INFLATING 哨兵态 ---| | | b. 提取线程 A 栈中的 DMW -------|----------------------------------| | c. 组装 ObjectMonitor 结构体 | | | d. 对象头写入 Monitor 绝对地址 -| (状态演变为 10 重量级锁) | | | | |-- 3. 执行 monitor-enter() ------| | | a. 自适应自旋失败 | | | b. 将自己封入 ObjectWaiter | | | c. 调用 os::park() 进入内核睡眠 | | | | | | |-- 4. 执行 synchronized 结束 ------| | | a. 发现对象头已非自身栈指针 | | | b. 顺着 10 标记找到 Monitor | | | c. 释放锁并 unpark 唤醒线程 B ----| v v v锁膨胀流程核心要点总结从轻量级锁到重量级锁的膨胀在系统层面引发了以下深刻的架构变化维度轻量级锁 (00)重量级锁 (10)同步核心机制用户态 CAS 指令 (lock cmpxchg)操作系统内核信号量/互斥锁 (sys_futex)内存开销线程栈帧上的Lock Record(空间极小)堆外分配的ObjectMonitor结构体 (包含队列、计数器)CPU 表现竞争失败时短暂自旋压榨 CPU竞争失败时挂起触发操作系统上下文切换 (Context Switch)哈希码存储复写到持有者线程的栈帧中复写到ObjectMonitor的_header字段中设计精妙点提炼INFLATING 哨兵机制锁膨胀本身是一个多步赋值的复杂过程如分配 Monitor、搬运哈希码、改写对象头。引入INFLATING()哨兵位能够让其他并发线程快速识别“该对象正在扩建中”从而主动让出 CPU 避免重复扩建极其优雅地解决了膨胀过程中的并发冲突。所有权无缝顺延在inflate阶段发起膨胀的线程非常慷慨地将ObjectMonitor-_owner直接设为了原轻量级锁持有者而不是据为己有。这种设计确保了即便锁发生了剧烈震荡原临界区内的线程也能毫无阻碍地执行完业务并正确释放。