markwrord状态变对CPU核心与Linux内核的影响剖析前言markwrord状态变对CPU核心与Linux内核的影响OpenJDK 8中markwrord 内存布局与状态定义偏向锁 (Biased Locking) 的底层行为与系统影响OpenJDK 8源码实现剖析CPU 核心与内核影响轻量级锁 (Lightweight Locking) 与 CPU CAS 锁总线/缓存行OpenJDK 8源码实现剖析CPU 核心与内核影响重量级锁 (Heavyweight Locking) 膨胀与 Linux 内核 Futex 协同OpenJDK 8源码实现剖析CPU 核心与内核影响1. CPU 核心层面的改变2. Linux 内核层面的改变markwrord 状态变化对系统级影响的横向对比前言本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限文中内容难免存在疏漏恳请读者不吝指正。markwrord状态变对CPU核心与Linux内核的影响OpenJDK 8中markwrord 内存布局与状态定义在 OpenJDK 8中markwrord的结构定义在src/share/vm/oops/markOop.hpp中。它是 Java 对象头Object Header的核心组成部分其大小与 CPU 的位数一致32位或64位。通过低几位的组合markwrord 实现了对“无锁”、“偏向锁”、“轻量级锁”、“重量级锁”和“GC标记”五种状态的复用。以下是markOop.hpp中关于 64 位架构下 markwrord 内存布局的源码定义// src/share/vm/oops/markOop.hpp// 64 bits:// --------// unused:25 hash:31 --| unused:1 age:4 biased_lock:1 lock:2 (normal object)// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)//// - biased_lock_bits 1, lock_bits 2// - lock 状态位映射:// 00 - Lightweight Locked (轻量级锁)// 01 - Unlocked / Biased (无锁或偏向锁由 biased_lock_bits 进一步区分)// 10 - Heavyweight Locked (重量级锁)// 11 - Marked for GC (GC 标记清除状态)classmarkOopDesc:publicoopDesc{private:// 返回不带任何锁标记的原始数值uintptr_tvalue()const{return(uintptr_t)this;}public:// 状态位常量定义enum{lock_bits2,biased_lock_bits1,max_hash_bitsBitsPerWord-lock_bits-biased_lock_bits-4-1,// 64-2-1-4-1 56 位 (包含 unused)hash_bits31,// 实际 hash 占 31 位age_bits4,epoch_bits2};// 状态掩码与位移enum{locked_value0,// 00unlocked_value1,// 01monitor_value2,// 10marked_value3,// 11biased_lock_pattern5// 101 (binary)};// 判断当前 markwrord 是否处于偏向锁状态boolhas_bias_pattern()const{return(value()(biased_lock_mask_in_place))biased_lock_pattern;}// 判断是否是重量级锁boolhas_monitor()const{return((value()monitor_value)!0);}};偏向锁 (Biased Locking) 的底层行为与系统影响偏向锁的目标是解决“无竞争”情况下的单线程锁重入问题。当一个锁对象第一次被某个线程获取时JVM 会将 markwrord 设为偏向模式并利用 CAS 将当前线程的 ID 写入 markwrord 中。OpenJDK 8源码实现剖析偏向锁的获取逻辑主要在src/share/vm/runtime/synchronizer.cpp和平台相关的汇编器如src/cpu/x86/vm/macroAssembler_x86.cpp中。// src/share/vm/runtime/synchronizer.cppvoidObjectSynchronizer::fast_enter(Handle obj,BasicLock*lock,boolattempt_rebias,TRAPS){if(UseBiasedLocking){if(!SafepointSynchronize::is_at_safepoint()){// 快速路径尝试获取偏向锁BiasedLocking::Condition condBiasedLocking::revoke_and_rebias(obj,attempt_rebias,THREAD);if(condBiasedLocking::BIAS_REVOKED_AND_REBIASED){return;// 偏向锁获取或重偏向成功直接返回}}else{assert(!attempt_rebias,can not rebias of safepoint);BiasedLocking::revoke_at_safepoint(obj);}}// 偏向锁未开启、或获取失败则进入轻量级锁逻辑slow_enter(obj,lock,THREAD);}在底层 x86 汇编层面macroAssembler_x86.cpp中的biased_locking_enter负责生成无锁总线消耗的检测指令// src/cpu/x86/vm/macroAssembler_x86.cppvoidMacroAssembler::biased_locking_enter(Register obj_reg,Register obj_header_reg,Register swap_reg,Register tmp_reg,boolswap_reg_contains_mark,Labeldone,Label*slow_case,BiasedLockingCounters*counters){// 1. 检查 markwrord 的低三位是否为 101 (偏向锁标记)movptr(obj_header_reg,Address(obj_reg,oopDesc::mark_offset_in_bytes()));movptr(swap_reg,obj_header_reg);andptr(swap_reg,markOopDesc::biased_lock_mask_in_place);cmpptr(swap_reg,markOopDesc::biased_lock_pattern);jcc(Assembler::notEqual,cas_label);// 不是偏向锁走向轻量级锁 CAS// 2. 检查偏向锁中的 Thread ID 是否指向当前线程movptr(swap_reg,r15_thread);// r15 在 x86_64 上存储着当前 JavaThread 指针xorptr(swap_reg,obj_header_reg);andptr(swap_reg,~((int32_t)markOopDesc::age_mask_in_place|markOopDesc::epoch_mask_in_place));jcc(Assembler::equal,done);// Thread ID 匹配锁重入成功完全没有原子指令// ... 偏向锁撤销或重偏向的慢速路径}CPU 核心与内核影响CPU 缓存一致性与总线流速当锁处于偏向状态且未发生竞争时获取和释放锁的过程不需要执行任何原子汇编指令如LOCK前缀指令。CPU 核心仅对本核的 L1/L2 Cache 进行常规的Read/Write操作。根据 MESI 协议该缓存行Cache Line保持在Modified (M)或Exclusive (E)状态不会向内核总线发送无效化Invalidate消息从而避免了缓存行回写与总线交通拥堵。Linux 内核影响完全发生在 JVM 用户态。Linux 内核对此无感知线程始终处于TASK_RUNNING状态不涉及系统调用与内核调度。全局安全点Safepoint代价如果另一个线程尝试获取已被偏向的锁JVM 必须撤销Revoke偏向锁。偏向锁撤销必须在Safepoint下进行。这时VM 线程会向所有 CPU 核心发送中断信号或设置安全点内存页屏蔽保护迫使所有 Java 线程进入暂停状态。Linux 内核需要将这些线程的上下文挂起切换到信号处理或安全点等待逻辑带来显著的进程级上下文切换开销。轻量级锁 (Lightweight Locking) 与 CPU CAS 锁总线/缓存行当偏向锁关闭或者多个线程交替交替获取锁无激烈竞争时markwrord 会转换为轻量级锁状态。OpenJDK 8源码实现剖析轻量级锁的核心在于在当前线程的栈帧Stack Frame中创建一个锁记录BasicObjectLock中的BasicLock并将对象的 markwrord 复制到该锁记录中称为 Displaced markwrord然后通过 CAS 将对象的 markwrord 指向该锁记录。// src/share/vm/runtime/synchronizer.cppvoidObjectSynchronizer::slow_enter(Handle obj,BasicLock*lock,TRAPS){markOop markobj-mark();if(mark-is_neutral()){// 如果当前是无锁状态 (001)// 1. 将原 markwrord 复制到线程栈帧的 Lock Record 中lock-set_displaced_header(mark);// 2. 通过 CAS 尝试将对象头的 markwrord 替换为指向 Lock Record 的指针if(Atomic::cmpxchg_ptr(lock,obj()-mark_addr(),mark)mark){return;// CAS 成功当前线程获取轻量级锁}}elseif(mark-has_locker()THREAD-is_lock_owned((address)mark-locker())){// 3. 如果是当前线程自身的锁重入lock-set_displaced_header(NULL);// 压入 NULL 标记代表重入return;}// 4. 存在锁竞争或者锁已被其他线程持有升级为重量级锁lock-set_displaced_header(markOopDesc::unused_mark());ObjectSynchronizer::inflate(THREAD,obj())-enter(THREAD);}其底层 CAS 最终调用src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp中的内联汇编// src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hppinlineintptr_tAtomic::cmpxchg_ptr(intptr_t exchange_value,volatileintptr_t*dest,intptr_t compare_value){intptr_t result;// __asm__ __volatile__ 嵌入汇编使用 lock cmpxchgq 指令__asm____volatile__(LOCK_IF_MP(%4)cmpxchgq %1,(%3):a(result):r(exchange_value),a(compare_value),r(dest),r(mp):cc,memory);returnresult;}CPU 核心与内核影响LOCK前缀与总线/缓存锁在现代 Intel/AMD CPU 上lock cmpxchgq指令不会无条件锁住物理系统总线。如果目标内存地址即对象头所在的缓存行已经缓存在当前 CPU 核心的 L1/L2 Cache 中CPU 会触发缓存锁定Cache Locking。MESI 协议与缓存行弹跳Cache Line Bouncing执行lock cmpxchgq的 CPU 核心会向内核总线发出RFO (Request For Ownership)信号将其他 CPU 核心中对应的缓存行状态从Shared (S)变更为Invalid (I)。执行核心将自身缓存行状态设为Modified (M)并独占写入。如果多个 CPU 核心上的线程频繁交替执行此 CAS 指令会导致该对象头所在的缓存行在不同的 CPU 核心之间频繁“弹跳”触发大量的 L1/L2 缓存未命中Cache Miss与数据总线同步流量拖慢 CPU 整体的执行流速。Memory Barrier (内存屏障)LOCK前缀指令具有隐式的全屏障Full Barrier作用它会刷新 CPU 的 Store Buffer 与 Load Buffer阻止指令重排。这会导致当前核心的流水线Pipeline被强制清空Flush暂时阻止了流水线的乱序执行Out-of-Order Execution能力。Linux 内核影响依然维持在用户态。虽然 CPU 产生了硬件级别的同步消耗但由于未调用系统调用Linux 内核不参与线程调度上下文切换数Context Switches不会增加。重量级锁 (Heavyweight Locking) 膨胀与 Linux 内核 Futex 协同当轻量级锁发生竞争或者 CAS 自旋超时JVM 会触发锁膨胀Inflation将 markwrord 的低两位修改为10并将其余位数指向一个多线程同步器——ObjectMonitor结构体。OpenJDK 8源码实现剖析锁膨胀逻辑位于src/share/vm/runtime/synchronizer.cpp的ObjectSynchronizer::inflate// src/share/vm/runtime/synchronizer.cppObjectMonitor*ObjectSynchronizer::inflate(Thread*Self,oop object){for(;;){markOop markobject-mark();// 情况 1已经是重量级锁了直接返回if(mark-has_monitor()){returnmark-monitor();}// 情况 2正在膨胀中说明有其他线程在操作当前线程稍作自旋等待if(markmarkOopDesc::INFLATING()){ReadStableMark(object);continue;}// 情况 3当前是轻量级锁尝试升级为重量级锁if(mark-has_locker()){ObjectMonitor*momAlloc(Self);// 从线程本地或全局队列分配一个 ObjectMonitorm-Recycle();m-_ResponsibleNULL;m-_recursions0;m-_SpinDurationObjectMonitor::Knob_SpinLimit;// 设置自旋参数// 将 markwrord 替换为 INFLATING 状态markOop cmp(markOop)Atomic::cmpxchg_ptr(markOopDesc::INFLATING(),object-mark_addr(),mark);if(cmp!mark){omRelease(Self,m,true);continue;// CAS 失败重试}// 填充 ObjectMonitor 的属性m-set_header(mark-displaced_header());m-set_owner(mark-locker());m-set_object(object);// 最终将对象头的 markwrord 指向 ObjectMonitor状态位设为 10object-release_set_mark(markOopDesc::encode(m));returnm;}// ... 情况 4无锁状态下的膨胀}}在获取ObjectMonitor时如果自旋失败最终会调用ObjectMonitor::EnterI并进入操纵系统级别的挂起逻辑// src/share/vm/runtime/objectMonitor.cppvoidObjectMonitor::EnterI(TRAPS){Thread*SelfTHREAD;// ... 尝试自旋获取锁// 将当前线程封装为 ObjectWaiter 节点并放入 _cxq 队列ObjectWaiternode(Self);Self-_ParkEvent-Reset();node._notified0;node.TStateObjectWaiter::TS_CXQ;ObjectWaiter*nxt;for(;;){node._nextnxt_cxq;if(Atomic::cmpxchg_ptr(node,_cxq,nxt)nxt)break;}// 阻塞线程的循环for(;;){if(TryLock(Self)0)break;// 再次尝试获取锁// 真正触发 OS 级别挂起if((SyncFlags2)_ResponsibleNULL){Atomic::cmpxchg_ptr(Self,_Responsible,NULL);}// 调用 os::PlatformEvent::park() 挂起线程Self-_ParkEvent-park();if(TryLock(Self)0)break;// ... 处理伪唤醒}}Linux 平台下的os::PlatformEvent::park()位于src/os/linux/vm/os_linux.cpp// src/os/linux/vm/os_linux.cppvoidos::PlatformEvent::park(){intstatus;// 原子操作将当前状态设为 0即需要挂起if(Atomic::xchg(_Event,0)0){pthread_mutex_lock(_mutex);// 获取底层的 POSIX 互斥锁while(_Event0){// 调用 Linux 线程库的 pthread_cond_wait底层映射为 Linux 内核的 futex 系统调用statuspthread_cond_wait(_cond,_mutex);}pthread_mutex_unlock(_mutex);}}CPU 核心与内核影响当轻量级锁升级为重量级锁或者直接在重量级锁上产生未命中自旋时系统层面会发生剧烈的震荡1. CPU 核心层面的改变PAUSE指令的密集调用在进入重量级锁的自旋优化Adaptive Spinning期间JVM 会密集执行PAUSE汇编指令。PAUSE通知 CPU 当前处于自旋等待循环CPU 会暂停流水线中指令的解码防止因内存依赖性冲突导致处理器在退出循环时清空流水线Pipeline Flush大幅降低自旋期间的 CPU 功耗和热量。TLB 与 Cache 局部性失效一旦自旋失败线程准备挂起CPU 必须保存当前执行流的寄存器上下文。当线程被唤醒并在另一个 CPU 核心上调度执行时由于跨核心迁移新核心的 L1/L2 缓存中完全没有该线程的数据Cache Cold Start并且由于进程/线程间地址转换可能带来的快表TLB刷新会导致短期内频繁发生 TLB Miss 和 Cache Miss。2. Linux 内核层面的改变用户态到内核态的上下文切换Context Switchpthread_cond_wait触发sys_futex系统调用通过syscall指令转换到特权级别 Ring 0。CPU 核心的堆栈指针从用户栈切换到内核栈保存通用寄存器群RAX, RCX, RDI 等到进程的pt_regs结构体。内核 O(1) 锁管理Futex Hash Bucket内核接收到FUTEX_WAIT请求后会根据_cond变量的虚拟地址在内核的futex_hash_bucket散列表中计算哈希值获取对应的平衡二叉树/链表槽位将当前线程的task_struct挂入该哈希桶的等待队列中。状态机转换与进程调度器Scheduler介入内核将该线程的task_struct-state状态从TASK_RUNNING改为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE。调用schedule()函数唤醒 Linux 调度器将其从当前 CPU 核心的运行队列runqueue中摘除。调度器选择下一个处于TASK_RUNNING状态的最高优先级线程将其寄存器上下文载入 CPU 核心完成一次主动上下文切换Voluntary Context Switch。markwrord 状态变化对系统级影响的横向对比下表总结了从无锁到重量级锁的演进过程中底层的硬件与内核性能指标变化锁状态markwrord 标志位核心汇编/系统调用CPU 缓存行MESI影响Linux 内核状态与开销典型性能瓶颈偏向锁101mov,cmp,and(无原子指令)无影响缓存行保持M/E状态无内核参与用户态纯 O(1) 操作多线程交替执行时的Safepoint 偏向撤销造成的系统停顿轻量级锁000lock cmpxchgq触发Cache Locking使其他核心缓存行变为I无内核参与线程保持TASK_RUNNING高频交替竞争时的缓存行弹跳Cache Bouncing与流水线清空重量级锁010PAUSE→ \rightarrow→sys_futex自旋期降低功耗挂起后发生Cache/TLB Cold Start触发Ring 3→ \rightarrow→Ring 0切换修改task_struct并引发调度频繁竞争引起的上下文切换Context Switch与内核态 CPU 占满
markwrord状态变对CPU核心与Linux内核的影响剖析
发布时间:2026/7/6 3:41:15
markwrord状态变对CPU核心与Linux内核的影响剖析前言markwrord状态变对CPU核心与Linux内核的影响OpenJDK 8中markwrord 内存布局与状态定义偏向锁 (Biased Locking) 的底层行为与系统影响OpenJDK 8源码实现剖析CPU 核心与内核影响轻量级锁 (Lightweight Locking) 与 CPU CAS 锁总线/缓存行OpenJDK 8源码实现剖析CPU 核心与内核影响重量级锁 (Heavyweight Locking) 膨胀与 Linux 内核 Futex 协同OpenJDK 8源码实现剖析CPU 核心与内核影响1. CPU 核心层面的改变2. Linux 内核层面的改变markwrord 状态变化对系统级影响的横向对比前言本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限文中内容难免存在疏漏恳请读者不吝指正。markwrord状态变对CPU核心与Linux内核的影响OpenJDK 8中markwrord 内存布局与状态定义在 OpenJDK 8中markwrord的结构定义在src/share/vm/oops/markOop.hpp中。它是 Java 对象头Object Header的核心组成部分其大小与 CPU 的位数一致32位或64位。通过低几位的组合markwrord 实现了对“无锁”、“偏向锁”、“轻量级锁”、“重量级锁”和“GC标记”五种状态的复用。以下是markOop.hpp中关于 64 位架构下 markwrord 内存布局的源码定义// src/share/vm/oops/markOop.hpp// 64 bits:// --------// unused:25 hash:31 --| unused:1 age:4 biased_lock:1 lock:2 (normal object)// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)//// - biased_lock_bits 1, lock_bits 2// - lock 状态位映射:// 00 - Lightweight Locked (轻量级锁)// 01 - Unlocked / Biased (无锁或偏向锁由 biased_lock_bits 进一步区分)// 10 - Heavyweight Locked (重量级锁)// 11 - Marked for GC (GC 标记清除状态)classmarkOopDesc:publicoopDesc{private:// 返回不带任何锁标记的原始数值uintptr_tvalue()const{return(uintptr_t)this;}public:// 状态位常量定义enum{lock_bits2,biased_lock_bits1,max_hash_bitsBitsPerWord-lock_bits-biased_lock_bits-4-1,// 64-2-1-4-1 56 位 (包含 unused)hash_bits31,// 实际 hash 占 31 位age_bits4,epoch_bits2};// 状态掩码与位移enum{locked_value0,// 00unlocked_value1,// 01monitor_value2,// 10marked_value3,// 11biased_lock_pattern5// 101 (binary)};// 判断当前 markwrord 是否处于偏向锁状态boolhas_bias_pattern()const{return(value()(biased_lock_mask_in_place))biased_lock_pattern;}// 判断是否是重量级锁boolhas_monitor()const{return((value()monitor_value)!0);}};偏向锁 (Biased Locking) 的底层行为与系统影响偏向锁的目标是解决“无竞争”情况下的单线程锁重入问题。当一个锁对象第一次被某个线程获取时JVM 会将 markwrord 设为偏向模式并利用 CAS 将当前线程的 ID 写入 markwrord 中。OpenJDK 8源码实现剖析偏向锁的获取逻辑主要在src/share/vm/runtime/synchronizer.cpp和平台相关的汇编器如src/cpu/x86/vm/macroAssembler_x86.cpp中。// src/share/vm/runtime/synchronizer.cppvoidObjectSynchronizer::fast_enter(Handle obj,BasicLock*lock,boolattempt_rebias,TRAPS){if(UseBiasedLocking){if(!SafepointSynchronize::is_at_safepoint()){// 快速路径尝试获取偏向锁BiasedLocking::Condition condBiasedLocking::revoke_and_rebias(obj,attempt_rebias,THREAD);if(condBiasedLocking::BIAS_REVOKED_AND_REBIASED){return;// 偏向锁获取或重偏向成功直接返回}}else{assert(!attempt_rebias,can not rebias of safepoint);BiasedLocking::revoke_at_safepoint(obj);}}// 偏向锁未开启、或获取失败则进入轻量级锁逻辑slow_enter(obj,lock,THREAD);}在底层 x86 汇编层面macroAssembler_x86.cpp中的biased_locking_enter负责生成无锁总线消耗的检测指令// src/cpu/x86/vm/macroAssembler_x86.cppvoidMacroAssembler::biased_locking_enter(Register obj_reg,Register obj_header_reg,Register swap_reg,Register tmp_reg,boolswap_reg_contains_mark,Labeldone,Label*slow_case,BiasedLockingCounters*counters){// 1. 检查 markwrord 的低三位是否为 101 (偏向锁标记)movptr(obj_header_reg,Address(obj_reg,oopDesc::mark_offset_in_bytes()));movptr(swap_reg,obj_header_reg);andptr(swap_reg,markOopDesc::biased_lock_mask_in_place);cmpptr(swap_reg,markOopDesc::biased_lock_pattern);jcc(Assembler::notEqual,cas_label);// 不是偏向锁走向轻量级锁 CAS// 2. 检查偏向锁中的 Thread ID 是否指向当前线程movptr(swap_reg,r15_thread);// r15 在 x86_64 上存储着当前 JavaThread 指针xorptr(swap_reg,obj_header_reg);andptr(swap_reg,~((int32_t)markOopDesc::age_mask_in_place|markOopDesc::epoch_mask_in_place));jcc(Assembler::equal,done);// Thread ID 匹配锁重入成功完全没有原子指令// ... 偏向锁撤销或重偏向的慢速路径}CPU 核心与内核影响CPU 缓存一致性与总线流速当锁处于偏向状态且未发生竞争时获取和释放锁的过程不需要执行任何原子汇编指令如LOCK前缀指令。CPU 核心仅对本核的 L1/L2 Cache 进行常规的Read/Write操作。根据 MESI 协议该缓存行Cache Line保持在Modified (M)或Exclusive (E)状态不会向内核总线发送无效化Invalidate消息从而避免了缓存行回写与总线交通拥堵。Linux 内核影响完全发生在 JVM 用户态。Linux 内核对此无感知线程始终处于TASK_RUNNING状态不涉及系统调用与内核调度。全局安全点Safepoint代价如果另一个线程尝试获取已被偏向的锁JVM 必须撤销Revoke偏向锁。偏向锁撤销必须在Safepoint下进行。这时VM 线程会向所有 CPU 核心发送中断信号或设置安全点内存页屏蔽保护迫使所有 Java 线程进入暂停状态。Linux 内核需要将这些线程的上下文挂起切换到信号处理或安全点等待逻辑带来显著的进程级上下文切换开销。轻量级锁 (Lightweight Locking) 与 CPU CAS 锁总线/缓存行当偏向锁关闭或者多个线程交替交替获取锁无激烈竞争时markwrord 会转换为轻量级锁状态。OpenJDK 8源码实现剖析轻量级锁的核心在于在当前线程的栈帧Stack Frame中创建一个锁记录BasicObjectLock中的BasicLock并将对象的 markwrord 复制到该锁记录中称为 Displaced markwrord然后通过 CAS 将对象的 markwrord 指向该锁记录。// src/share/vm/runtime/synchronizer.cppvoidObjectSynchronizer::slow_enter(Handle obj,BasicLock*lock,TRAPS){markOop markobj-mark();if(mark-is_neutral()){// 如果当前是无锁状态 (001)// 1. 将原 markwrord 复制到线程栈帧的 Lock Record 中lock-set_displaced_header(mark);// 2. 通过 CAS 尝试将对象头的 markwrord 替换为指向 Lock Record 的指针if(Atomic::cmpxchg_ptr(lock,obj()-mark_addr(),mark)mark){return;// CAS 成功当前线程获取轻量级锁}}elseif(mark-has_locker()THREAD-is_lock_owned((address)mark-locker())){// 3. 如果是当前线程自身的锁重入lock-set_displaced_header(NULL);// 压入 NULL 标记代表重入return;}// 4. 存在锁竞争或者锁已被其他线程持有升级为重量级锁lock-set_displaced_header(markOopDesc::unused_mark());ObjectSynchronizer::inflate(THREAD,obj())-enter(THREAD);}其底层 CAS 最终调用src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp中的内联汇编// src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hppinlineintptr_tAtomic::cmpxchg_ptr(intptr_t exchange_value,volatileintptr_t*dest,intptr_t compare_value){intptr_t result;// __asm__ __volatile__ 嵌入汇编使用 lock cmpxchgq 指令__asm____volatile__(LOCK_IF_MP(%4)cmpxchgq %1,(%3):a(result):r(exchange_value),a(compare_value),r(dest),r(mp):cc,memory);returnresult;}CPU 核心与内核影响LOCK前缀与总线/缓存锁在现代 Intel/AMD CPU 上lock cmpxchgq指令不会无条件锁住物理系统总线。如果目标内存地址即对象头所在的缓存行已经缓存在当前 CPU 核心的 L1/L2 Cache 中CPU 会触发缓存锁定Cache Locking。MESI 协议与缓存行弹跳Cache Line Bouncing执行lock cmpxchgq的 CPU 核心会向内核总线发出RFO (Request For Ownership)信号将其他 CPU 核心中对应的缓存行状态从Shared (S)变更为Invalid (I)。执行核心将自身缓存行状态设为Modified (M)并独占写入。如果多个 CPU 核心上的线程频繁交替执行此 CAS 指令会导致该对象头所在的缓存行在不同的 CPU 核心之间频繁“弹跳”触发大量的 L1/L2 缓存未命中Cache Miss与数据总线同步流量拖慢 CPU 整体的执行流速。Memory Barrier (内存屏障)LOCK前缀指令具有隐式的全屏障Full Barrier作用它会刷新 CPU 的 Store Buffer 与 Load Buffer阻止指令重排。这会导致当前核心的流水线Pipeline被强制清空Flush暂时阻止了流水线的乱序执行Out-of-Order Execution能力。Linux 内核影响依然维持在用户态。虽然 CPU 产生了硬件级别的同步消耗但由于未调用系统调用Linux 内核不参与线程调度上下文切换数Context Switches不会增加。重量级锁 (Heavyweight Locking) 膨胀与 Linux 内核 Futex 协同当轻量级锁发生竞争或者 CAS 自旋超时JVM 会触发锁膨胀Inflation将 markwrord 的低两位修改为10并将其余位数指向一个多线程同步器——ObjectMonitor结构体。OpenJDK 8源码实现剖析锁膨胀逻辑位于src/share/vm/runtime/synchronizer.cpp的ObjectSynchronizer::inflate// src/share/vm/runtime/synchronizer.cppObjectMonitor*ObjectSynchronizer::inflate(Thread*Self,oop object){for(;;){markOop markobject-mark();// 情况 1已经是重量级锁了直接返回if(mark-has_monitor()){returnmark-monitor();}// 情况 2正在膨胀中说明有其他线程在操作当前线程稍作自旋等待if(markmarkOopDesc::INFLATING()){ReadStableMark(object);continue;}// 情况 3当前是轻量级锁尝试升级为重量级锁if(mark-has_locker()){ObjectMonitor*momAlloc(Self);// 从线程本地或全局队列分配一个 ObjectMonitorm-Recycle();m-_ResponsibleNULL;m-_recursions0;m-_SpinDurationObjectMonitor::Knob_SpinLimit;// 设置自旋参数// 将 markwrord 替换为 INFLATING 状态markOop cmp(markOop)Atomic::cmpxchg_ptr(markOopDesc::INFLATING(),object-mark_addr(),mark);if(cmp!mark){omRelease(Self,m,true);continue;// CAS 失败重试}// 填充 ObjectMonitor 的属性m-set_header(mark-displaced_header());m-set_owner(mark-locker());m-set_object(object);// 最终将对象头的 markwrord 指向 ObjectMonitor状态位设为 10object-release_set_mark(markOopDesc::encode(m));returnm;}// ... 情况 4无锁状态下的膨胀}}在获取ObjectMonitor时如果自旋失败最终会调用ObjectMonitor::EnterI并进入操纵系统级别的挂起逻辑// src/share/vm/runtime/objectMonitor.cppvoidObjectMonitor::EnterI(TRAPS){Thread*SelfTHREAD;// ... 尝试自旋获取锁// 将当前线程封装为 ObjectWaiter 节点并放入 _cxq 队列ObjectWaiternode(Self);Self-_ParkEvent-Reset();node._notified0;node.TStateObjectWaiter::TS_CXQ;ObjectWaiter*nxt;for(;;){node._nextnxt_cxq;if(Atomic::cmpxchg_ptr(node,_cxq,nxt)nxt)break;}// 阻塞线程的循环for(;;){if(TryLock(Self)0)break;// 再次尝试获取锁// 真正触发 OS 级别挂起if((SyncFlags2)_ResponsibleNULL){Atomic::cmpxchg_ptr(Self,_Responsible,NULL);}// 调用 os::PlatformEvent::park() 挂起线程Self-_ParkEvent-park();if(TryLock(Self)0)break;// ... 处理伪唤醒}}Linux 平台下的os::PlatformEvent::park()位于src/os/linux/vm/os_linux.cpp// src/os/linux/vm/os_linux.cppvoidos::PlatformEvent::park(){intstatus;// 原子操作将当前状态设为 0即需要挂起if(Atomic::xchg(_Event,0)0){pthread_mutex_lock(_mutex);// 获取底层的 POSIX 互斥锁while(_Event0){// 调用 Linux 线程库的 pthread_cond_wait底层映射为 Linux 内核的 futex 系统调用statuspthread_cond_wait(_cond,_mutex);}pthread_mutex_unlock(_mutex);}}CPU 核心与内核影响当轻量级锁升级为重量级锁或者直接在重量级锁上产生未命中自旋时系统层面会发生剧烈的震荡1. CPU 核心层面的改变PAUSE指令的密集调用在进入重量级锁的自旋优化Adaptive Spinning期间JVM 会密集执行PAUSE汇编指令。PAUSE通知 CPU 当前处于自旋等待循环CPU 会暂停流水线中指令的解码防止因内存依赖性冲突导致处理器在退出循环时清空流水线Pipeline Flush大幅降低自旋期间的 CPU 功耗和热量。TLB 与 Cache 局部性失效一旦自旋失败线程准备挂起CPU 必须保存当前执行流的寄存器上下文。当线程被唤醒并在另一个 CPU 核心上调度执行时由于跨核心迁移新核心的 L1/L2 缓存中完全没有该线程的数据Cache Cold Start并且由于进程/线程间地址转换可能带来的快表TLB刷新会导致短期内频繁发生 TLB Miss 和 Cache Miss。2. Linux 内核层面的改变用户态到内核态的上下文切换Context Switchpthread_cond_wait触发sys_futex系统调用通过syscall指令转换到特权级别 Ring 0。CPU 核心的堆栈指针从用户栈切换到内核栈保存通用寄存器群RAX, RCX, RDI 等到进程的pt_regs结构体。内核 O(1) 锁管理Futex Hash Bucket内核接收到FUTEX_WAIT请求后会根据_cond变量的虚拟地址在内核的futex_hash_bucket散列表中计算哈希值获取对应的平衡二叉树/链表槽位将当前线程的task_struct挂入该哈希桶的等待队列中。状态机转换与进程调度器Scheduler介入内核将该线程的task_struct-state状态从TASK_RUNNING改为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE。调用schedule()函数唤醒 Linux 调度器将其从当前 CPU 核心的运行队列runqueue中摘除。调度器选择下一个处于TASK_RUNNING状态的最高优先级线程将其寄存器上下文载入 CPU 核心完成一次主动上下文切换Voluntary Context Switch。markwrord 状态变化对系统级影响的横向对比下表总结了从无锁到重量级锁的演进过程中底层的硬件与内核性能指标变化锁状态markwrord 标志位核心汇编/系统调用CPU 缓存行MESI影响Linux 内核状态与开销典型性能瓶颈偏向锁101mov,cmp,and(无原子指令)无影响缓存行保持M/E状态无内核参与用户态纯 O(1) 操作多线程交替执行时的Safepoint 偏向撤销造成的系统停顿轻量级锁000lock cmpxchgq触发Cache Locking使其他核心缓存行变为I无内核参与线程保持TASK_RUNNING高频交替竞争时的缓存行弹跳Cache Bouncing与流水线清空重量级锁010PAUSE→ \rightarrow→sys_futex自旋期降低功耗挂起后发生Cache/TLB Cold Start触发Ring 3→ \rightarrow→Ring 0切换修改task_struct并引发调度频繁竞争引起的上下文切换Context Switch与内核态 CPU 占满