背景
并发编程学习中,过了遍,偏向锁、轻量级锁、重量级锁之后,自己想梳理下他们之间的切换过程,然后在线程ID、锁记录、monitor存储位置这块有点讲不清楚,于是查询了下,在此做下记录
线程ID、锁记录,monitor对象引用关系
在Java中,线程ID、Monitor(监视器)的状态以及其他与锁相关的元数据并不是直接存储在对象头中的。相反,这些信息是通过对象头的Mark Word和外部结构(如Monitor对象)间接关联的。具体来说,Mark Word是对象头的一部分,它根据锁的状态动态变化,存储不同的信息。而Monitor本身是一个独立的结构,通常不直接存储在对象头中。
1. 对象头的组成
对象头是每个Java对象在内存中的一个固定部分,通常包含以下两个主要部分:
- Mark Word:64位JVM中通常是64位,32位JVM中通常是32位。Mark Word用于存储对象的元数据信息。
- Klass Pointer:指向对象的类元数据(Class Metadata),即描述对象类型的元信息。
Mark Word的内容
Mark Word的内容并不是固定的,而是根据对象的状态动态变化的。它可能包含以下几种信息:
- 哈希码 (Hash Code):默认情况下,Mark Word存储对象的哈希码。
- GC年龄 (GC Age):记录对象在垃圾回收过程中存活的次数。
- 锁状态 (Lock State):当对象被锁定时,Mark Word会存储锁的相关信息,如偏向线程的ID、指向轻量级锁记录的指针或指向重量级锁(Monitor)的指针。
- 偏向锁 (Biased Locking):如果启用了偏向锁,Mark Word会存储偏向线程的ID,表示该对象偏向于某个特定线程。
- 轻量级锁 (Lightweight Locking):在轻量级锁状态下,Mark Word存储指向栈中锁记录的指针。
- 重量级锁 (Heavyweight Locking):在重量级锁状态下,Mark Word存储指向Monitor对象的指针。
2. Monitor(监视器)
Monitor是一个独立的结构,通常不直接存储在对象头中。Monitor负责管理锁的状态,并提供线程间的同步机制。Monitor的主要组成部分包括:
- 入口列表 (Entry List):包含所有试图获取锁但未能成功获取的线程。
- 等待集 (Wait Set):包含所有调用
wait()方法后被挂起的线程。 - Owner Thread:当前持有锁的线程。
- Reentrancy Count:记录当前线程已经进入临界区的次数(用于可重入锁)。
- Recursion Depth:记录锁的递归深度。
Monitor的存储位置
- 轻量级锁:在轻量级锁状态下,Mark Word中存储的是指向栈中锁记录的指针。锁记录保存在当前线程的栈中,而不是对象头中。
- 重量级锁:在重量级锁状态下,Mark Word中存储的是指向Monitor对象的指针。Monitor对象通常存储在堆中,而不是对象头中。
3. 线程ID的存储
- 偏向锁:当对象处于偏向锁状态时,Mark Word中会存储偏向线程的ID。这意味着只有当锁是偏向锁时,线程ID才会直接存储在对象头的Mark Word中。
- 轻量级锁:在轻量级锁状态下,Mark Word中存储的是指向栈中锁记录的指针,锁记录中包含了当前持有锁的线程ID。
- 重量级锁:在重量级锁状态下,Mark Word中存储的是指向Monitor对象的指针,Monitor对象中包含了当前持有锁的线程ID。
4. 总结:线程ID、Monitor和对象头的关系
-
线程ID:
- 在偏向锁状态下,线程ID直接存储在对象头的Mark Word中。
- 在轻量级锁和重量级锁状态下,线程ID并不直接存储在对象头中,而是存储在栈中的锁记录(轻量级锁)或Monitor对象(重量级锁)中。
-
Monitor:
- Monitor本身是一个独立的结构,通常不直接存储在对象头中。在轻量级锁状态下,Monitor的信息存储在当前线程的栈中;在重量级锁状态下,Monitor对象存储在堆中,对象头的Mark Word中只存储指向Monitor对象的指针。
-
对象头:
- 对象头的Mark Word根据锁的状态动态变化,存储不同的信息。它可能是哈希码、GC年龄、偏向线程ID、指向锁记录的指针或指向Monitor对象的指针。
5. 示意图
+---------------------+
| 对象头 |
+---------------------+
| Mark Word | ----> 动态内容(哈希码、偏向线程ID、锁记录指针、Monitor指针等)
+---------------------+
| Klass Pointer | ----> 指向类元数据
+---------------------+
+---------------------+
| Monitor (堆) | (重量级锁)
+---------------------+
| Owner Thread | ----> 当前持有锁的线程ID
| Entry List | ----> 等待获取锁的线程
| Wait Set | ----> 等待条件的线程
| Reentrancy Count | ----> 递归计数
+---------------------+
+---------------------+
| 锁记录 (栈) | (轻量级锁)
+---------------------+
| Owner Thread | ----> 当前持有锁的线程ID
| Displaced Mark Word | ----> 被替换的Mark Word
+---------------------+
6. 锁升级过程中的变化
- 无锁状态:Mark Word存储哈希码、GC年龄等信息。
- 偏向锁状态:Mark Word存储偏向线程的ID。
- 轻量级锁状态:Mark Word存储指向栈中锁记录的指针,锁记录中包含当前持有锁的线程ID。
- 重量级锁状态:Mark Word存储指向Monitor对象的指针,Monitor对象中包含当前持有锁的线程ID。
7. 结论
- 线程ID 并不是直接存储在对象头中的,而是根据锁的状态存储在不同的地方:
- 在偏向锁状态下,线程ID存储在对象头的Mark Word中。
- 在轻量级锁状态下,线程ID存储在栈中的锁记录中。
- 在重量级锁状态下,线程ID存储在Monitor对象中。
- Monitor 本身是一个独立的结构,通常不直接存储在对象头中。在轻量级锁状态下,Monitor的信息存储在栈中;在重量级锁状态下,Monitor对象存储在堆中,对象头的Mark Word中只存储指向Monitor对象的指针。
理解这些细节有助于你更好地理解Java中的锁机制和对象内存布局,从而编写更高效的多线程代码。