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)) { |
2.2 ObjectMonitor::wait() 实现细节
等待队列:使用循环双向链表实现等待队列
- 位置:
objectMonitor.cpp:1529-1531 - 关键操作:
AddWaiter(&node)将线程加入等待队列
park操作:通过ParkEvent进入操作系统级等待
if (node._notified == 0) { |
状态管理:线程状态转换为_thread_blocked
ThreadBlockInVM tbivm(jt)将线程状态从_thread_in_vm转换为_thread_blocked- 析构时恢复为
_thread_in_vm
3. 类初始化死锁的形成机制
3.1 死锁条件
线程T1: 持有ClassA.init_lock → 等待ClassB.init_lock |
3.2 调用链分析
应用层: Class.forName() → <clinit>() → 静态变量访问 |
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中状态重置 |
状态枚举定义:
RUNNABLE = JVMTI_THREAD_STATE_ALIVE + JVMTI_THREAD_STATE_RUNNABLE |
4.3 差异根源
- gstack:显示实际操作系统线程状态
- jstack:显示Java线程模型抽象状态
- 矛盾原因:当monitor不存在或所有权信息不可用时,jstack将状态重置为RUNNABLE
5. TencentKona-8 ObjectMonitor关键实现
5.1 等待队列管理
_WaitSetLock (自旋锁保护等待队列) |
5.2 唤醒机制
notify() → _WaitSetLock → DequeueWaiter() |
5.3 性能优化
- 自旋锁保护等待队列,避免重量级锁开销
OrderAccess::fence()保证内存可见性- 双重检查减少锁竞争
6. 类初始化锁的特殊性
6.1 锁对象选择
- 使用
int[0]而非普通Object - 原因:需要在Java调用间保持锁持有
- 避免与用户代码冲突
6.2 锁生命周期
类加载 → 创建init_lock(int[0]) |
6.3 重入支持
bool is_reentrant_initialization(Thread* self) { |
允许同一线程递归初始化同一类
7. 死锁诊断与避免
7.1 诊断方法
- jstack检测:查找多个线程等待相同类初始化
- gstack确认:验证线程在
ObjectMonitor::wait()中 - 依赖分析:检查类间静态依赖关系
7.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 可能差异点
- 锁对象创建:int[0] vs 专用锁对象
- 等待算法:CLH队列 vs MCS队列 vs 其他
- park实现:futex vs pthread条件变量
- 状态管理:线程状态转换细节
8.2 统一标准
- JVM规范定义类初始化语义
- Object.wait()/notify()标准行为
- 线程状态映射规范
9. 结论
- 类初始化死锁是JVM内部锁机制在特定场景下的体现
- ObjectMonitor::wait() 是连接JVM线程模型与操作系统线程模型的桥梁
- gstack/jstack差异源于不同抽象层次的状态表示
- 诊断关键:结合应用层、JVM层、系统层三重堆栈分析
- 根本原因:类间静态初始化循环依赖 + JVM类初始化锁机制
10. 技术价值
- 深度理解:揭示了JVM类初始化机制的底层实现
- 诊断能力:提供了类初始化死锁的多维度诊断方法
- 优化方向:为JVM性能优化提供理论基础
- 避免策略:为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锁机制 + 系统级等待