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 会对当前 JavaThreadLockStack 状态再次进行运行时断言与处理。

核心锁膨胀逻辑 (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)

  1. Mark Word 状态硬切换:对象的 Mark Word 从 Fast-Locked (000) 状态被 CAS 原子更新为 Inflated (10),锁元数据直接托管给 C-Heap 上的 ObjectMonitor
  2. LockStack 占用解除:后续第 9 次及更深层次的递归加锁,直接递增 ObjectMonitor::_recursions 计数器,绝对不会向 LockStack 压入第 9 个对象指针
  3. 解锁路径平滑过渡:退出嵌套锁时,C2 生成的 fast_unlock 会优先检查 Mark Word。当发现状态位为 10 (Inflated) 时,跳过 LockStack 弹出逻辑,直接走 ObjectMonitor::exit() 递减重入计数。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值