HotSpot的轻量锁与膨胀后的ObjectMonitor状态重建原理

HotSpot的轻量锁与膨胀后的ObjectMonitor状态重建原理剖析

  • 前言
  • 轻量锁与膨胀后的ObjectMonitor状态重建原理
    • [1. Deopt 锁重建的总体拓扑与处理逻辑](#1. Deopt 锁重建的总体拓扑与处理逻辑)
    • [2. HotSpot C++ 源代码详细分析](#2. HotSpot C++ 源代码详细分析)
    • [3. 两种物理锁状态(`LockStack` vs. `ObjectMonitor`)的重建细节](#3. 两种物理锁状态(LockStack vs. ObjectMonitor)的重建细节)
      • [场景 A:轻量锁状态(Fast-Locked `000` & 在 `LockStack` 中)](#场景 A:轻量锁状态(Fast-Locked 000 & 在 LockStack 中))
      • [场景 B:膨胀锁状态(Inflated `10` & `ObjectMonitor` 持有)](#场景 B:膨胀锁状态(Inflated 10 & ObjectMonitor 持有))
    • [4. 关键边缘情况与约束保障](#4. 关键边缘情况与约束保障)
      • [1. `LockStack` 溢出(Capacity Boundary = 8)](#1. LockStack 溢出(Capacity Boundary = 8))
      • [2. Deopt 过程中的 GC Safepoint 与 Oop 映射](#2. Deopt 过程中的 GC Safepoint 与 Oop 映射)
    • [5. 状态转换总结矩阵](#5. 状态转换总结矩阵)

前言

本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限,文中内容难免存在疏漏,恳请读者不吝指正。

轻量锁与膨胀后的ObjectMonitor状态重建原理

当 C2 发生 Deoptimization(解优化)时,HotSpot 是如何处理 LockStack 中的轻量锁与膨胀后的 ObjectMonitor 状态重建的。

在 JDK 21 引入 -XX:+UseLightweightLocking 机制后,HotSpot JVM 的锁架构发生了重大调整:BasicLock(栈锁记录)不再保存 Displaced Mark Word,线程持有的轻量级锁统一由 JavaThread::_lock_stack(Thread-Local 的对象指针数组,容量上限为 8)管理

当 C2 编译器执行 Deoptimization(解优化) 时,编译栈帧(Compiled Frame)被解包(Unpack)并重建为一至多个解释器栈帧(Interpreter Frame)。此时,HotSpot 必须依据 C2 在编译期记录的调试元数据(ScopeDesc -> MonitorValue),重建这些解释器帧对应的锁物理状态(LockStackObjectMonitor)与逻辑句柄(BasicObjectLock)。


1. Deopt 锁重建的总体拓扑与处理逻辑

在 Deoptimization 过程中,C2 编译帧中的锁分为两类,其重建路径存在本质区别:

复制代码
                        ┌──────────────────────────────────────────────┐
                        │     C2 Compiled Frame Deoptimization         │
                        │   (vframeArray::allocate_array & unpack)     │
                        └──────────────────────┬───────────────────────┘
                                               │
                                               ▼
                        ┌──────────────────────────────────────────────┐
                        │      解析 ScopeDesc 中的 MonitorValue        │
                        └──────────────────────┬───────────────────────┘
                                               │
               ┌───────────────────────────────┴───────────────────────────────┐
               │                                                               │
               ▼ (mv->is_eliminated() == true)                                 ▼ (mv->is_eliminated() == false)
┌──────────────────────────────────────────────┐                ┌──────────────────────────────────────────────┐
│        被 C2 消除的锁 (Eliminated Lock)       │                │       物理已持有的锁 (Physical Lock)          │
├──────────────────────────────────────────────┤                ├──────────────────────────────────────────────┤
│ 物理上未加锁(不在 LockStack/Monitor 中)     │                │ 物理上已加锁(在 LockStack 或 Monitor 中)     │
│ 记录至 deferred_joins 延迟链表                │                │ 保持当前 Mark Word 和 LockStack 物理状态不变 │
│ 触发 Deoptimization::relock_objects          │                │ 仅在解包的解释器帧中构建 BasicObjectLock 槽位 │
│ 调用 LightweightSynchronizer::enter_for      │                │ 使 BasicObjectLock::_obj 指向该 oop          │
└──────────────────────┬───────────────────────┘                └──────────────────────┬───────────────────────┘
                       │                                                               │
                       ▼                                                               ▼
┌──────────────────────────────────────────────┐                ┌──────────────────────────────────────────────┐
│  重新分配物理锁凭证:                        │                │ 物理状态一致性对齐:                          │
│  1. 若 LockStack 未满且无竞争:              │                │  a) 若处于 Fast-Locked (000):                │
│     - CAS 置 markWord 为 Fast-Locked (000)   │                │     - LockStack 保留指针;                   │
│     - 压入 JavaThread._lock_stack            │                │     - 解释器 exit 时直接从 LockStack 弹出    │
│  2. 若 LockStack 已满(>=8)或发生竞争:       │                │  b) 若处于 Inflated (10):                   │
│     - 强行膨胀为 ObjectMonitor               │                │     - ObjectMonitor::_owner 保持当前线程;   │
│     - 置 markWord 为 Inflated (10)           │                │     - 解释器 exit 时走向 ObjectMonitor::exit │
└──────────────────────┬───────────────────────┘                └──────────────────────┬───────────────────────┘
                       │                                                               │
                       └───────────────────────────────┬────────────────────────────┘
                                                       │
                                                       ▼
                        ┌──────────────────────────────────────────────┐
                        │      Reconstructed Interpreter Frames        │
                        │ 解释器字节码对齐完成,可以安全执行 monitorexit│
                        └──────────────────────────────────────────────┘

2. HotSpot C++ 源代码详细分析

解优化过程中的锁状态重建分布在 vframeArray.cppdeoptimization.cpplightweightSynchronizer.cpp 三个核心源文件中。

步骤一:编译帧解包与锁分类 (vframeArray.cpp)

在 C2 解包的第一阶段,vframeArrayElement::unpack_on_stack 遍历 ScopeDesc 中的 MonitorValue。此处决定哪些锁需要物理重建,哪些锁仅需建立解释器帧指针映射。

cpp 复制代码
// src/hotspot/share/runtime/vframeArray.cpp
void vframeArrayElement::unpack_on_stack(int caller_actual_parameters,
                                         int callee_parameters,
                                         int callee_locals,
                                         methodHandle method,
                                         int is_top_frame,
                                         int is_bottom_frame,
                                         int exec_mode) {
  // ...
  // 遍历编译期记录的所有锁元数据 (MonitorValue)
  for (int index = 0; index < monitors()->length(); index++) {
    MonitorValue* mv = monitors()->at(index);
    ScopeValue* owner_sv = mv->owner();
    
    // 1. 从 C2 作用域值中提取当前持有的 Java 对象 (oop)
    StackValue* value = StackValue::create_stack_value(frame_map, reg_map, owner_sv);
    Handle obj(Thread::current(), value->get_obj()());

    // 2. 在物理重建的解释器栈帧中预留 BasicObjectLock 槽位
    BasicObjectLock* sc = stack_child->interpreter_frame_monitor_begin() - index - 1;
    sc->set_obj(obj()); // 将解释器帧的 monitor 关联指向该 oop

    // 3. 区分"被 C2 逃逸分析消除的锁"与"物理已持有的锁"
    if (mv->is_eliminated()) {
      // 被消除的锁在硬件物理上并没有加锁(Mark Word 仍为 Unlocked,LockStack 中无此 oop)。
      // 必须加入 deferred_joins 队列,延迟到所有栈帧解包完成后,统一通过 relock_objects 重新加锁。
      deferred_joins->append(new monitorArrayElement(obj(), sc->lock()));
    } else {
      // 物理已持有的锁:在 C2 运行期间已经执行过物理加锁(即对象已经在 LockStack 中,
      // 或 Mark Word 已经为 Fast-Locked 000 / Inflated 10)。
      // 此时【完全不需要】重新 CAS 或修改 LockStack,其物理锁凭证依然有效!
    }
  }
  // ...
}

步骤二:重新锁定被消除的锁 (deoptimization.cpp)

对于 mv->is_eliminated() == true 的锁,在解释器接管运行前必须强行补做加锁操作,否则解释器后续执行 monitorexit 字节码时会报 IllegalMonitorStateException

cpp 复制代码
// src/hotspot/share/runtime/deoptimization.cpp
void Deoptimization::relock_objects(JavaThread* thread, GrowableArray<monitorArrayElement*>* objects) {
  assert(objects != nullptr, "must be");
  
  for (int i = 0; i < objects->length(); i++) {
    monitorArrayElement* mon = objects->at(i);
    Handle obj(thread, mon->owner());
    BasicLock* lock = mon->lock();

    // 在 JDK 21 -XX:+UseLightweightLocking 模式下,调用 LightweightSynchronizer 重建物理锁状态
    if (UseLightweightLocking) {
      LightweightSynchronizer::enter_for(obj, lock, thread);
    } else {
      // 传统路径 (Displaced Mark Word / Legacy Heavyweight Lock)
      ObjectSynchronizer::enter(obj, lock, thread);
    }
  }
}

步骤三:LockStack 补加锁与强行膨胀逻辑 (lightweightSynchronizer.cpp)

LightweightSynchronizer::enter_for 是 JDK 21 Deopt 重建物理锁凭证的核心入口:

cpp 复制代码
// src/hotspot/share/runtime/lightweightSynchronizer.cpp
void LightweightSynchronizer::enter_for(Handle obj, BasicLock* lock, JavaThread* current) {
  assert(current->is_Java_thread(), "must be JavaThread");
  
  markWord mark = obj->mark();

  // -------------------------------------------------------------------------
  // 情况 1:对象处于无锁状态 (Unlocked 001)
  // -------------------------------------------------------------------------
  if (mark.is_unlocked()) {
    // 检查当前线程的 Thread-Local LockStack 是否有剩余容量 (容量上限为 8)
    if (current->lock_stack().can_push()) {
      // 尝试通过 CAS 将 Mark Word 从 Unlocked (001) 切换为 Fast-Locked (000)
      markWord new_mark = markWord::prototype().set_fast_locked();
      if (obj->cas_set_mark(new_mark, mark) == mark) {
        // CAS 成功:将 oop 压入当前 JavaThread 的 _lock_stack 中!
        current->lock_stack().push(obj());
        return; // 成功恢复为轻量级锁 (LockStack 状态)
      }
    }
  }

  // -------------------------------------------------------------------------
  // 情况 2:LockStack 已满 (>= 8) 或发生并发竞争 / 对象已被膨胀
  // -------------------------------------------------------------------------
  // 如果 LockStack 容量溢出或 CAS 失败,不能丢弃锁,必须退化为锁膨胀 (Inflate)
  ObjectMonitor* monitor = ObjectSynchronizer::inflate(current, obj(), inflate_cause_vm_internal);
  
  // 强行把 ObjectMonitor 的 _owner 指向当前 Deopt 的线程,完成膨胀锁重建
  monitor->enter(current);
}

3. 两种物理锁状态(LockStack vs. ObjectMonitor)的重建细节

针对在 Deopt 前物理上已经持有的锁,HotSpot 如何保证物理状态与解包后的解释器帧平滑对接:

场景 A:轻量锁状态(Fast-Locked 000 & 在 LockStack 中)

  1. 物理凭证保持 :在 C2 编译代码执行期间,fast_lock 汇编指令已经将 oop 压入了 JavaThread::_lock_stack,Mark Word 为 000
  2. Deopt 动作
  • 不会触发 enter_for_lock_stack 的内容保持原封不动
  • 解包逻辑仅需在 Interpreter Frame 的栈底填入 BasicObjectLock 结构,将 _obj 属性指向该 oop
  1. **解释器后续执行 monitorexit**
  • 解释器执行到 monitorexit 时,通过 LightweightSynchronizer::exit 慢速/快速路径,直接调用 current->lock_stack().remove(obj()) 将该 oop_lock_stack 中移除,并通过 CAS 将对象 Mark Word 恢复为 Unlocked (001)

场景 B:膨胀锁状态(Inflated 10 & ObjectMonitor 持有)

  1. 物理凭证保持 :对象可能在 C2 运行期间调用了 Object.wait()、计算了 identityHashCode 或发生了多线程争用,已膨胀为 ObjectMonitor。对象的 Mark Word 指向 ObjectMonitor*,且 ObjectMonitor::_owner == thread
  • 注意 :在 JDK 21 中,**膨胀后的对象指针会被移出 LockStack**,不再占用 LockStack 的 8 个槽位。
  1. Deopt 动作
  • ObjectMonitor 实例及其 _owner 保持不变。
  • Interpreter Frame 分配 BasicObjectLock 槽位,关联该 oop
  1. **解释器后续执行 monitorexit**
  • 解释器读取 Mark Word 低 2 位发现为 10 (has_monitor),直接调用 ObjectMonitor::exit,减少重入计数或释放 _owner

4. 关键边缘情况与约束保障

1. LockStack 溢出(Capacity Boundary = 8)

如果一个方法在 C2 阶段消除了 10 个对象的锁(例如在循环中对非逃逸对象加锁),当发生 Deopt 时,Deoptimization::relock_objects 依次调用 LightweightSynchronizer::enter_for

  • 前 8 个对象通过 CAS 被设置为 Fast-Locked (000) 并压入 LockStack
  • 第 9 个和第 10 个对象调用 enter_for 时,current->lock_stack().can_push() 返回 false
  • enter_for **自动触发 ObjectSynchronizer::inflate**,将第 9 和第 10 个对象膨胀为 ObjectMonitor,将 _owner 设为当前线程。这完美解决了 Thread-Local LockStack 容量有限的问题。

2. Deopt 过程中的 GC Safepoint 与 Oop 映射

在解包 C2 栈帧构建解释器帧及调用 relock_objects 期间,对象句柄全部通过 Handle(thread, oop) 包装。如果 enter_for 中发生的 ObjectSynchronizer::inflate 触发了 C-Heap 申请或 GC,GC 根扫描能够正确更新 LockStackHandle 中的对象指针,防止悬挂指针(Dangling Pointer)。


5. 状态转换总结矩阵

锁在 C2 编译帧中的原始状态 物理 Mark Word (低 3 位) JavaThread::_lock_stack 状态 Deopt 解包时的核心操作 解包后的解释器执行行为
被 C2 锁消除 (Eliminated) 001 (Unlocked) 不存在该 oop 调用 relock_objects -> enter_for 补加锁 重新压入 LockStack(若满了则膨胀为 ObjectMonitor
未消除轻量锁 (Fast-Locked) 000 (Fast-Locked) 已经存在该 oop 不修改物理状态 ,仅构建解释器 BasicObjectLock 映射 解释器 monitorexit 时从 LockStack 弹出 oop
未消除膨胀锁 (Inflated) 10 (Inflated) 不存在该 oop 不修改物理状态 ,保留 ObjectMonitor::_owner 解释器 monitorexit 时走 ObjectMonitor::exit
相关推荐
2603_965148111 小时前
AI+API选品:下一代智能商务助手雏形已现
java·大数据·数据库·人工智能·数据挖掘
CoderYanger1 小时前
A.每日一题:835. 图像重叠
java·开发语言·程序人生·leetcode·面试·职场和发展·学习方法
北冥有鱼被烹1 小时前
perftest 之 ib_write_bw --use-null-mr 参数详解:用 NULL MR 做 RDMA 带宽测试
java
2601_963870211 小时前
基于SSM的特产代购系统
java·前端
一只小透明啊啊啊啊1 小时前
Socket、AIDL、SOME/IP
c++·mcu
牧瀬クリスだ1 小时前
YAML配置详解:SpringBoot实战指南
java·spring·yml
Su5731 小时前
RuoYi + RAGFlow 搭建私有化知识库完整集成实践(二)
java
wechatbot8881 小时前
极客互动企业微信 SCRM 自动运营平台效果实测
java·微信·企业微信·rpa
需要8261 小时前
JVM 内存区域与对象创建:一次 GC 从哪来
java·jvm·spring boot·spring·servlet·tomcat