偏向锁、轻量级锁、重量级锁对应的线程ID、锁记录,monitor对象引用都是存储在对象头里的Markword中吗?

153 阅读5分钟

背景

并发编程学习中,过了遍,偏向锁、轻量级锁、重量级锁之后,自己想梳理下他们之间的切换过程,然后在线程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中的锁机制和对象内存布局,从而编写更高效的多线程代码。