JDK21中ObjectMonitor膨胀机制剖析
–
前言
本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限,文中内容难免存在疏漏,恳请读者不吝指正。
ObjectMonitor膨胀机制
在JDK 21的 LockStack机制下,当递归加锁或嵌套加锁导致LockStack个元素发生溢出时,C2 编译器是如何ObjectMonito膨胀原理剖析分析。
在 JDK 21 实现的轻量级锁机制(-XX:+UseLightweightLocking)中,线程私有的 LockStack 被设计为固定容量(LockStack::CAPACITY = 8)的引用数组。当嵌套或递归加锁深度超过 8 次时,LockStack 发生上界溢出。C2 编译器通过在 JIT 编译生成的 fast_lock 宏汇编指令流的前置判断中插装容量边界检查(Bounds Check),直接断开 Fast-Path 并强行跳转至慢速路径 Stub,触发 C++ 运行时的锁膨胀(Inflation)。
以下是 C2 编译器与 HotSpot 运行时在源代码层面的完整交互与分析路径。
1. C2 汇编层:边界插装与 Fast-Path 切断
C2 编译器在 PhaseMacroExpand 阶段将抽象的 FastLockNode 展开为特定架构的物理机器码。在 x86_64 平台下,入口函数为 C2_MacroAssembler::fast_lock。
核心源代码分析 (src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp)
void C2_MacroAssembler::fast_lock(Register obj, Register box, Register tmp, Register tmp2) {
assert(UseLightweightLocking, "only with lightweight locking");
Label slow_path;
// 1. 读取当前 JavaThread(r15) 内部 _lock_stack 结构的 _top 偏移量
// _top 记录当前 LockStack 栈顶相对于 _base 数组的字节偏移
movl(tmp, Address(r15_thread, JavaThread::lock_stack_top_offset()));
// 2. 边界检查:比较 _top 与 LockStack::end_offset()
// LockStack::end_offset() 为常量 (LockStack::CAPACITY * sizeof(oop) + base_offset)
cmpl(tmp, LockStack::end_offset());
// 3. 溢出条件分支:当 _top >= end_offset (即已压入 8 个 oop),直接跳转至 slow_path
jge(slow_path);
// -------------------------------------------------------------------------
// 以下为 LockStack 未满时的 Fast-Path 逻辑:
// a) 验证对象 Mark Word 是否为 Unlocked (001) 或已处于本线程 LockStack 中
// b) 执行 CAS 将 Mark Word 更新为 Locked (000)
// c) 将 obj 指针写入 r15_thread->_lock_stack._base[_top],并递增 _top
// -------------------------------------------------------------------------
...
// 4. slow_path 汇编标号:用于承接溢出及 CAS 失败的指令流
bind(slow_path);
// 最终由 OptoRuntime 生成 JRT (Java Runtime) 调用,保存 Caller-saved 寄存器帧,
// 压入 obj 和 box 参数,并 call 进 SharedRuntime::complete_monitor_locking_C
}
JIT 编译生成的物理 x86_64 机器码分析
# C2 JIT 在生成方法体入口/同步块时的真实汇编指令流 (x86_64)
mov 0x2d0(%r15), %eax # 1. 从 Thread Local Storage (%r15) 加载 JavaThread::_lock_stack._top
cmp $0x310, %eax # 2. 与 0x310 (即 LockStack 的容量上限字节偏移) 进行比较
jge 0x00007fff8d2e4120 # 3. 溢出跳转:当 %eax >= 0x310 时,直接跳过 CAS,走向 .L_slow_path
2. C2 Runtime Stub 桥接与上下文保护
当代码跳入慢速路径,C2 会通过 OptoRuntime::complete_monitor_locking_Java 建立 JVM 异常/运行时 Stub 帧,保存所有通用寄存器与 FPU 状态,随后调用位于 SharedRuntime 的 C++ 运行时入口。
源代码入口 (src/hotspot/share/runtime/sharedRuntime.cpp)
JRT_ENTRY_NO_ASYNC(void, SharedRuntime::complete_monitor_locking_C(oopDesc* obj, BasicLock* lock, JavaThread* current))
// 将物理 oop 指针包裹为 C++ Handle 句柄,防止慢速路径触发 Safepoint 时 GC 移动对象
Handle h_obj(current, obj);
// 1. 明确断言当前开启了轻量级锁机制
if (UseLightweightLocking) {
// 2. 调用轻量级同步器 slow-path 核心逻辑
LightweightSynchronizer::enter(h_obj, lock, current);
} else {
// Legacy 传统 BasicLock 路径
ObjectSynchronizer::enter(h_obj, lock, current);
}
JRT_END
3. C++ 运行时:膨胀与 ObjectMonitor 挂载
进入 HotSpot 的 C++ 逻辑层后,LightweightSynchronizer::enter 会对当前 JavaThread 的 LockStack 状态再次进行运行时断言与处理。
核心锁膨胀逻辑 (src/hotspot/share/runtime/lightweightSynchronizer.cpp)
void LightweightSynchronizer::enter(Handle obj, BasicLock* lock, JavaThread* current) {
markWord mark = obj->mark();
// 1. 显式校验:若 LockStack 已经满足 is_full() (即 top >= CAPACITY)
if (current->lock_stack().is_full()) {
// 直接进入膨胀并加锁流程,不再尝试任何快速锁重试
inflate_and_enter(obj, lock, current);
return;
}
// 2. 若 LockStack 未满但因多线程 CAS 竞争失败,在此处进行有限自旋或备用处理
...
}
void LightweightSynchronizer::inflate_and_enter(Handle obj, BasicLock* lock, JavaThread* current) {
// 1. 调用 ObjectSynchronizer::inflate 分配或获取对应的 ObjectMonitor 结构
// 原因标记为 inflate_cause_vm_internal (内部逻辑强制膨胀)
ObjectMonitor* monitor = ObjectSynchronizer::inflate(current, obj(), inflate_cause_vm_internal);
// 2. 调用 ObjectMonitor 实例的 enter 方法,由 Monitor 负责维护等待队列与重入计数
monitor->enter(current);
// 3. 将对象的 Mark Word 原子替换为 Inflated 标记 (指向 ObjectMonitor 的指针,尾部状态位 10)
}
LockStack 容量判断细节 (src/hotspot/share/runtime/lockStack.hpp)
class LockStack {
static const int CAPACITY = 8; // 硬编码的锁栈容量限制
uint32_t _top; // 物理偏移指针
oop _base[CAPACITY]; // 存储持锁对象引用的数组
public:
bool is_full() const {
// 当 _top 达到 CAPACITY * sizeof(oop) 的物理大小或 8 个 count 时返回 true
return _top >= end_offset();
}
};
4. 膨胀后的锁状态转移机制
当 LockStack 溢出导致锁膨胀为 ObjectMonitor 后,对象头与 LockStack 的交互机制发生关键转变:
[LockStack 溢出前]
JavaThread._lock_stack: [obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8] (Full)
Mark Word: [ Thread ID | 000 (Fast-Locked) ]
│ (第 9 次嵌套加锁:C2 Bounds Check 触发 JGE 跳转)
▼
[锁膨胀膨胀阶段]
1. 分配 C-Heap ObjectMonitor 结构体
2. Mark Word CAS 替换为: [ ObjectMonitor* | 10 (Inflated) ]
│
▼
[锁重入解耦]
第 9 层及更深层级的 synchronized 不再占用 LockStack!
ObjectMonitor->_recursions 递增 (+1)
- Mark Word 状态硬切换:对象的 Mark Word 从
Fast-Locked (000)状态被 CAS 原子更新为Inflated (10),锁元数据直接托管给 C-Heap 上的ObjectMonitor。 - LockStack 占用解除:后续第 9 次及更深层次的递归加锁,直接递增
ObjectMonitor::_recursions计数器,绝对不会向LockStack压入第 9 个对象指针。 - 解锁路径平滑过渡:退出嵌套锁时,C2 生成的
fast_unlock会优先检查 Mark Word。当发现状态位为10 (Inflated)时,跳过LockStack弹出逻辑,直接走ObjectMonitor::exit()递减重入计数。

308

被折叠的 条评论
为什么被折叠?



