并发4️⃣管程③Monitor
Monitor 是操作系统提供的、负责处理 synchronized 的组件。
在学习 Monitor 之前,要了解 Java 对象头 的概念。
1、对象组成
以 32 位虚拟机为例
Java 对象由三部分组成:对象头、实例数据、对齐补充。

1.1、对象头
对象头的结构
-
Mark Word:存储对象运行时记录(如 hashcode、GC 分代年龄、锁状态标志、线程 ID 等)。
-
Klass Word:元数据指针,指向方法区的 instanceKlass 实例。
-
array length:数组类型的对象特有,表示数组长度。

1.2、Mark Word(❗)
对象处于不同状态时,Mark Word 有所不同。
末尾 2 位表示锁标记(不加锁、偏向锁、轻量级锁、重量级锁、垃圾回收标志)
-
32 位虚拟机
|---------------------------------------------------|--------------------| | Mark Word (32 bits) | State | |---------------------------------------------------|--------------------| | hashcode:25 | age:4 | biased_lock:0 | 01 | Normal | |---------------------------------------------------|--------------------| | thread:23 | epoch:2 | age:4 | biased_lock:1 | 01 | Biased | |---------------------------------------------------|--------------------| | ptr_to_lock_record:30 | 00 | Lightweight Locked | |---------------------------------------------------|--------------------| | ptr_to_heavyweight_monitor:30 | 10 | Heavyweight Locked | |---------------------------------------------------|--------------------| | | 11 | Marked for GC | |---------------------------------------------------|--------------------| -
64 位虚拟机
|-------------------------------------------------------|--------------------| | Mark Word (64 bits) | State | |-------------------------------------------------------|--------------------| |unused:25|hashcode:31|unused:1|age:4|biased_lock:0| 01 | Normal | |-------------------------------------------------------|--------------------| | thread:54 | epoch:2 |unused:1|age:4|biased_lock:1| 01 | Biased | |-------------------------------------------------------|--------------------| | ptr_to_lock_record:62 | 00 | Lightweight Locked | |-------------------------------------------------------|--------------------| | ptr_to_heavyweight_monitor:62 | 10 | Heavyweight Locked | |-------------------------------------------------------|--------------------| | | 11 | Marked for GC | |-------------------------------------------------------|--------------------|
1.3、oop-klass 模型(*)
Hotspot 使用 oop-klass 模型 表示 Java 类和对象。
| instanceKlass | instanceOopDesc | |
|---|---|---|
| 创建时期 | 类加载阶段,JVM 将字节码文件(.class)加载到方法区 |
new 创建对象时,JVM 会创建 instanceOopDesc |
| 说明 | 表示类的元数据,包括常量池、成员变量、方法等结构 | 表示对象实例,实例存放在堆区,对象引用存放在栈区 |
| 指针维护 | 属性 _java_mirror 存储 java.lang.Class 实例的地址 |
对象头的 Klass Word 存储 instanceKlass 的地址 |
代表
java.lang.Class的对象实例
JVM 不把 instanceKlass 暴露给 Java,而是创建一个单独的 instanceOopDesc 实例(代表 java.lang.Class 对象)
-
该对象实例称为 instanceKlass 的 Java 镜像
-
instanceKlass 维护了一个指向镜像的指针(即
_java_mirror) -
指针关系

2、Monitor 原理(❗)
JVM 的同步是基于进入和退出 Monitor 对象来实现的。
- 每个 Java 对象都可以关联一个 Monitor 对象,Monitor 随着 Java 对象而创建和销毁。
- Monitor 对象是 C++ 实现的,依赖于操作系统。
- 会导致线程上下文切换,导致用户态和内核态的切换,影响性能,
2.1、Monitor
管程、监视器
-
当使用
synchronized给对象加锁时(重量级),对象头中的 Mark Word 就被设置为指向 Monitor 对象的指针。 -
不加
synchronized的对象不会关联 Monitor。 -
不同 Java 对象关联的是不同的 Monitor。

2.2、Monitor 结构
结构
| 含义 | 对应线程状态 | |
|---|---|---|
| owner | 锁的持有者,正在执行同步代码块 (同一时刻,Monitor 只能有一个 Owner) |
RUNNABLE |
| EntryList | 由于锁已被其它线程获取,尝试获取锁失败的阻塞线程 | BLOCKED |
| WaitSet | 锁的持有者由于条件不满足,主动释放锁,进入等待状态 | WAITING |
Waitset 和 EntryList 的区别
| Waitset | EntryList | |
|---|---|---|
| 线程状态 | WAITING | BLOCKED |
| 进入条件 | 已获得锁的线程,由于条件不满足而调用 wait() |
尝试获得锁时,由于锁已被其它线程获取而阻塞 |
| 唤醒时机 | Owner 调用 notify() 或 notifyAll() |
Owner 释放锁 |
| 注意 | 唤醒后不会直接成为 Owner,而是进入 EntryList 竞争锁 | 若有多个等待线程,则竞争锁(非公平) |
2.2、图解
-
以普通对象为例(非数组类型,区别在于对象头)
-
注:以下案例针对同一个对象实例的 Monitor。
synchronized(obj){ ... }
线程 t1 执行到
synchronized(obj){}代码块
-
根据对象头的
Mark Word找到关联的 Monitor。 -
判断 Monitor 的
Owner为空,将其设置为当前线程 t1,进入临界区执行同步代码块(RUNNABLE)
线程 t2 执行到
synchronized(obj){}代码块
-
根据对象头的
Mark Word找到关联的 Monitor,判断 Monitor 的Owner不为空 -
t2 进入
EntryList,变成BLOCKED状态。 -
此时如果有 t3、t4 线程也执行到
synchronized(obj){},也会经历 t2 的过程。
若 Monitor 的
Owner(t1)执行过程中,条件不满足(涉及wait/notify)
-
t1 主动释放锁,进入
WaitSet,变成WAITING状态。
-
此时其它线程可以成为竞争锁,成为
Owner。 -
若某个获取了 obj 对象锁的线程执行了
notify(),说明 t1 的执行条件已满足。 -
线程 t1 退出
WaitSet,进入EntryList竞争锁(而不是直接成为Owner)。
当线程 t1 执行完同步代码块内容
将 Owner 置为 null,唤醒 EntryList 中的阻塞线程。
-
若
EntryList中只有 t2,则唤醒 t2 ,t2 获得锁。 -
若
EntryList中有多个线程,则唤醒所有阻塞线程来竞争锁(RUNNABLE)。-
竞争是非公平的(先阻塞的未必先获得)
-
竞争到锁的线程成为新的
Owner,竞争失败的锁再次进入BLOCKED状态。
-
3、synchronized 字节码
Monitor 加锁前会复制一份被加锁对象的引用,保证出现异常时也能正常解锁。
3.1、示例代码
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized (lock) {
counter++;
}
}
3.2、字节码
-
仅展示字节码指令、异常表
-
常用字节码指令:字节码技术 2.2
Stack=2, locals=3, args_size=1 0 getstatic #2 3 dup # 复制 4 astore_1 # 将复制的引用至存储到slot1 5 monitorenter # 将lock对象的Mark Word置为指向Monitor指针 6 getstatic #3 9 iconst_1 10 iadd 11 putstatic #3 14 aload_1 15 monitorexit # 将lock对象的Mark Word重置,唤醒EntryList 16 goto 24 19 astore_2 # 将异常存储到slot2 20 aload_1 # 加载slot1的对象引用 21 monitorexit 22 aload_2 23 athrow # 抛出异常 24 return Exception table: from to target type 6 16 19 any 19 22 19 any
3.2.1、正常流程分析
正常流程(关键步骤说明)
dup:复制 lock 对象引用,存放到局部变量表(用于解锁)。monitorenter:将对象关联 Monitor(Mark Word设为 Monitor 对象地址),进入临界区执行同步代码块。monitorexit:重置Mark Word(Owner 设为 null),唤醒EntryList的阻塞线程。- 没有异常,跳到第 24 行字节码指令,return 结束。
3.2.2、异常分析
JVM 根据异常表,监视 6-16、19-22 行的字节码指令。
任意一行指令发生 any 异常(任意类型),跳转到第 19 行。
- 将异常存储到 slot2
- 加载局部变量表 slot1 的 lock 引用,进行解锁。
- 抛出异常并结束。
3.3、说明
synchronized修饰对象时,才会生成对应的 monitorenter/monitorexit 指令。synchronzied修饰方法时,不会生成这对指令,而是生成一个ACC0SYNCHRONIZED标志。

浙公网安备 33010602011771号