Java 类初始化死锁分析报告(2)————ObjectMonitor实现深度分析报告

1. 概述

本报告基于对TencentKona-8 JDK源码的深度分析,揭示了类初始化死锁的底层机制、ObjectMonitor实现原理以及gstack与jstack输出差异的根本原因。

2. 核心发现

2.1 类初始化锁的创建与使用

锁创建:每个Java类在创建镜像时分配一个int[0]数组作为初始化锁

  • 位置:javaClasses.cpp:562-563
  • 代码:typeArrayOop r = oopFactory::new_typeArray(T_INT, 0, CHECK);

锁使用:通过ObjectLocker包装类提供锁操作

  • 位置:instanceKlass.cpp:849-850
  • 代码:ObjectLocker ol(init_lock, THREAD, init_lock != NULL);

等待机制:采用双重检查锁定模式

while(this_oop->is_being_initialized() && !this_oop->is_reentrant_initialization(self)) {
wait = true;
ol.waitUninterruptibly(CHECK); // 关键等待点
}

2.2 ObjectMonitor::wait() 实现细节

等待队列:使用循环双向链表实现等待队列

  • 位置:objectMonitor.cpp:1529-1531
  • 关键操作:AddWaiter(&node) 将线程加入等待队列

park操作:通过ParkEvent进入操作系统级等待

if (node._notified == 0) {
if (millis <= 0) {
Self->_ParkEvent->park(); // 无限期等待
} else {
ret = Self->_ParkEvent->park(millis); // 超时等待
}
}

状态管理:线程状态转换为_thread_blocked

  • ThreadBlockInVM tbivm(jt) 将线程状态从_thread_in_vm转换为_thread_blocked
  • 析构时恢复为_thread_in_vm

3. 类初始化死锁的形成机制

3.1 死锁条件

线程T1: 持有ClassA.init_lock → 等待ClassB.init_lock
线程T2: 持有ClassB.init_lock → 等待ClassA.init_lock

3.2 调用链分析

应用层: Class.forName() → <clinit>() → 静态变量访问
JVM层: InstanceKlass::initialize() → initialize_impl()
锁层: init_lock() → ObjectLockerwaitUninterruptibly()
OS层: ObjectMonitor::wait() → ParkEvent::park() → pthread_cond_wait()

4. gstack与jstack输出差异解析

4.1 gstack显示 ObjectMonitor::wait()

原因:gstack捕获操作系统级线程状态

  • 线程调用PlatformEvent::park()进入系统级等待
  • 等待在条件变量或futex上
  • gstack显示线程在ObjectMonitor::wait()系统调用中

4.2 jstack显示 RUNNABLE

原因:JVM线程状态映射机制

JVM内部状态

  • JavaThreadState::_thread_blocked (值10)
  • ThreadBlockInVM设置此状态

Java层映射

// 在ThreadSnapshot中状态重置
if (obj() == NULL) {
_thread_status = java_lang_Thread::RUNNABLE;
}

状态枚举定义

RUNNABLE = JVMTI_THREAD_STATE_ALIVE + JVMTI_THREAD_STATE_RUNNABLE
IN_OBJECT_WAIT = JVMTI_THREAD_STATE_ALIVE +
JVMTI_THREAD_STATE_WAITING +
JVMTI_THREAD_STATE_WAITING_INDEFINITELY +
JVMTI_THREAD_STATE_IN_OBJECT_WAIT

4.3 差异根源

  • gstack:显示实际操作系统线程状态
  • jstack:显示Java线程模型抽象状态
  • 矛盾原因:当monitor不存在或所有权信息不可用时,jstack将状态重置为RUNNABLE

5. TencentKona-8 ObjectMonitor关键实现

5.1 等待队列管理

_WaitSetLock (自旋锁保护等待队列)

AddWaiter() 添加等待者

exit() 释放monitor

park() 进入等待

5.2 唤醒机制

notify() → _WaitSetLock → DequeueWaiter()

准备唤醒线程

unpark() 唤醒等待线程

reenter() 重新获取monitor

5.3 性能优化

  • 自旋锁保护等待队列,避免重量级锁开销
  • OrderAccess::fence() 保证内存可见性
  • 双重检查减少锁竞争

6. 类初始化锁的特殊性

6.1 锁对象选择

  • 使用int[0]而非普通Object
  • 原因:需要在Java调用间保持锁持有
  • 避免与用户代码冲突

6.2 锁生命周期

类加载 → 创建init_lock(int[0])
类初始化 → 使用init_lock同步
初始化完成 → set_init_lock(NULL)
类卸载 → GC回收锁对象

6.3 重入支持

bool is_reentrant_initialization(Thread* self) {
return _init_thread == self;
}

允许同一线程递归初始化同一类

7. 死锁诊断与避免

7.1 诊断方法

  1. jstack检测:查找多个线程等待相同类初始化
  2. gstack确认:验证线程在ObjectMonitor::wait()
  3. 依赖分析:检查类间静态依赖关系

7.2 避免策略

  1. 设计层面

    • 避免静态初始化块中的循环依赖
    • 使用懒加载替代静态初始化
    • 分离初始化与业务逻辑
  2. 代码层面

    // 避免
    class A { static final B b = new B(); }
    class B { static final A a = new A(); }

    // 推荐
    class A { private static B b; static B getB() { if(b==null) b=new B(); return b; } }

8. 厂商实现对比展望

8.1 可能差异点

  1. 锁对象创建:int[0] vs 专用锁对象
  2. 等待算法:CLH队列 vs MCS队列 vs 其他
  3. park实现:futex vs pthread条件变量
  4. 状态管理:线程状态转换细节

8.2 统一标准

  • JVM规范定义类初始化语义
  • Object.wait()/notify()标准行为
  • 线程状态映射规范

9. 结论

  1. 类初始化死锁是JVM内部锁机制在特定场景下的体现
  2. ObjectMonitor::wait() 是连接JVM线程模型与操作系统线程模型的桥梁
  3. gstack/jstack差异源于不同抽象层次的状态表示
  4. 诊断关键:结合应用层、JVM层、系统层三重堆栈分析
  5. 根本原因:类间静态初始化循环依赖 + JVM类初始化锁机制

10. 技术价值

  1. 深度理解:揭示了JVM类初始化机制的底层实现
  2. 诊断能力:提供了类初始化死锁的多维度诊断方法
  3. 优化方向:为JVM性能优化提供理论基础
  4. 避免策略:为Java应用设计提供实践指导

附录:关键源码位置参考

组件 文件路径 关键行号
类初始化入口 instanceKlass.cpp:574 initialize()
初始化实现 instanceKlass.cpp:837 initialize_impl()
初始化锁 instanceKlass.cpp:518 init_lock()
ObjectLocker synchronizer.hpp:161 类定义
ObjectMonitor等待 objectMonitor.cpp:1472 wait()
ParkEvent park.hpp:118 类定义
类锁创建 javaClasses.cpp:562 new_typeArray()
线程状态映射 threadService.cpp:805 RUNNABLE设置
Java线程状态 javaClasses.hpp:393 ThreadStatus枚举

分析完成时间:基于TencentKona-8 JDK源码分析
核心发现:类初始化死锁 = 应用层循环依赖 + JVM锁机制 + 系统级等待