Java锁膨胀机制之轻量级锁到重量级锁源码剖析
2026/6/11 14:17:46 网站建设 项目流程

轻量级锁到重量级锁源码剖析

  • 前言
  • 轻量级锁到重量级锁源码剖析
    • 一、 锁膨胀的核心诱因与总体演进图景
    • 二、 核心源码剖析与极致注释说明
      • 1. 状态判定的源头:`markOop.hpp`
      • 2. 锁膨胀终极无锁状态机:`synchronizer.cpp`
      • 3. 重量级锁的入场争夺战:`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源码文件:

  1. hotspot/src/share/vm/oops/markOop.hpp:定义对象头的膨胀中哨兵态(INFLATING)及重量级标记。
  2. hotspot/src/share/vm/runtime/synchronizer.cpp:锁膨胀的核心状态机实现(ObjectSynchronizer::inflate)。
  3. hotspot/src/share/vm/runtime/objectMonitor.cpp:重量级锁的抢占、自适应自旋及线程挂起(ObjectMonitor::enter)。

二、 核心源码剖析与极致注释说明

1. 状态判定的源头:markOop.hpp

在向重量级锁演进时,对象头会经历一个特殊的临界状态——“膨胀中”(INFLATING)。

// 文件物理路径: hotspot/src/share/vm/oops/markOop.hppclassmarkOopDesc:publicoopDesc{public:enum{locked_value=0,// 00: 轻量级锁unlocked_value=1,// 01: 普通无锁monitor_value=2,// 10: 重量级锁 (ObjectMonitor*)marked_value=3,// 11: GC 标记biased_lock_pattern=5// 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 mark=object->mark();assert(!mark->has_bias_pattern(),"偏向锁必须在此前已被完全撤销");// =====================================================================// CASE 1: 已经是重量级锁状态 (10)// =====================================================================if(mark->has_monitor()){ObjectMonitor*inf=mark->monitor();assert(inf->header()->is_neutral(),"监视器内部保存的初始头必须是无锁状态");assert(inf->object()==object,"监视器关联的对象一致性校验");returninf;// 快捷路径:别的线程已经完成了膨胀,直接返回现成的监视器}// =====================================================================// CASE 2: 其它线程正在执行膨胀 (INFLATING 哨兵态)// =====================================================================if(mark==markOopDesc::INFLATING()){// 极其精妙的防御性设计:发现有同行在抢先扩建,当前线程不能横加干涉// ReadStableMark 内部会通过短暂的自旋或 yield 让出 CPU 周期,等待那个正在膨胀的线程完工ReadStableMark(object);continue;// 再次循环,届时将步入 CASE 1}// =====================================================================// CASE 3: 核心升级路径 —— 当前对象正处于轻量级锁状态 (00)// =====================================================================if(mark->has_locker()){// 1. 从当前线程的私有内存块(ObjectMonitor Allocator)或全局空闲列表里分配一个干净的 ObjectMonitorObjectMonitor*m=omAlloc(Self);m->Recycle();m->_Responsible=NULL;m->_recursions=0;m->_SpinDuration=ObjectMonitor::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 dmw=mark->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*m=omAlloc(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*Self=THREAD;// 1. 尝试一次快速的 CAS 抢锁,将 _owner 从 NULL 变更为当前线程指针void*cur=Atomic::cmpxchg_ptr(Self,&_owner,NULL);if(cur==NULL){return;// 抢锁成功,直接返回,进入临界区}// 2. 如果锁已被占,检查是不是当前线程自己重入if(cur==Self){_recursions++;// 重入计数器加一return;}// 3. 检查当前锁的拥有者是不是原轻量级锁拥有者的“栈地址”// 如果是,说明当前线程就是那个“原轻量级锁持有者”,它是第一次步入重量级锁的判罚场if(Self->is_lock_owned((address)cur)){assert(_recursions==0,"internal invariant");_recursions=1;_owner=Self;// 成功将所有权从“栈指针”肉身替换为真正的“线程对象指针”return;}// =====================================================================// 进入真正的竞争深水区// =====================================================================Self->set_current_pending_monitor(this);// 4. 激活自适应自旋优化 (Adaptive Spinning)// 在决定挂起自身前,先利用短时间的 CPU 轮询进行最后的挣扎,试图等待 _owner 释放if(Knob_SpinEarly>0&&TrySpin(Self)>0){assert(_owner==Self,"invariant");Self->set_current_pending_monitor(NULL);return;// 自旋期间原锁持有者释放了锁,当前线程幸运地截获了重量级锁}// 5. 自旋依然失败,必须进入阻塞状态{// 将当前线程封装为一个 ObjectWaiter 节点ObjectWaiternode(Self);Self->_ParkEvent->Reset();node._notified=0;node.TState=ObjectWaiter::TS_CXQ;// 标记状态为准备进入多线程竞争队列// 通过无锁原子 CAS 操作,将当前线程节点推入到 ObjectMonitor 的 _cxq (Contention Queue) 队列头部ObjectWaiter*nxt;for(;;){node._next=nxt=_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字段中

设计精妙点提炼:

  1. INFLATING 哨兵机制:锁膨胀本身是一个多步赋值的复杂过程(如分配 Monitor、搬运哈希码、改写对象头)。引入INFLATING()哨兵位能够让其他并发线程快速识别“该对象正在扩建中”,从而主动让出 CPU 避免重复扩建,极其优雅地解决了膨胀过程中的并发冲突。
  2. 所有权无缝顺延:inflate阶段,发起膨胀的线程非常慷慨地将ObjectMonitor->_owner直接设为了原轻量级锁持有者,而不是据为己有。这种设计确保了即便锁发生了剧烈震荡,原临界区内的线程也能毫无阻碍地执行完业务并正确释放。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询