一、Java对象头
由于Java面向对象的思想,在JVM中需要大量存储对象,存储时为了实现一些额外的功能,需要在对象中添加一些标记字段用于增强对象功能,这些标记字段组成了对象头。
对象头包含两部分:运行时元数据(Mark Word)和类型指针(Klass Word)
以32位虚拟机为例:
普通对象:
数组对象:数组对象还需要记录数组长度
其中,运行时元数据Mark Word结构为:
哈希值(HashCode)
,可以看作是堆中对象的地址GC分代年龄(年龄计数器)
(用于新生代from/to区晋升老年代的标准, 阈值为15)- 锁状态标志 (用于JDK1.6对synchronized的优化 -> 轻量级锁)
- 线程持有的锁
- 偏向线程ID (用于JDK1.6对synchronized的优化 -> 偏向锁)
- 偏向时间戳
类型指针: 指向类元数据InstanceKlass,确定该对象所属的类型。指向的其实是方法区中存放的类元信息
二、Monitor(锁)
Monitor被翻译为监视器或管程;
每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的Mark Word 中就被设置指向 Monitor 对象的指针(将对象与Monitor对象关联)。
图解:
如下图所示,我们有一个临界区代码,当Thread2执行到synchronized(obj),访问共享资源的时候:
- 首先会将synchronized中的锁对象中对象头的MarkWord去尝试指向操作系统提供的
Monitor
对象,让锁对象中的MarkWord和Monitor对象相关联. 如果关联成功, 将obj对象头中的MarkWord
的对象状态
从01改为10。 - 因为该Monitor没有和其他的obj的MarkWord相关联,所以
Thread2
就成为了该Monitor
的Owner(所有者)。 - 然后,又来了一个
Thread1
执行synchronized(obj)代码,它首先会检查是否能执行临界区代码,即检查obj是否关联了Montior,此时已经有关联了, 它就会去看看该Montior有没有所有者(Owner), 发现有所有者了(Thread2);Thread1
也会和该Monitor关联, 该线程就会进入到它的EntryList(阻塞队列),EntryList是一个列表,若此时Thread3也执行到synchronized(obj)代码,也会进入阻塞队列。 - 当
Thread2
执行完临界区
代码后, Monitor的Owner(所有者)
就空出来了. 此时就会通知
Monitor中的EntryList阻塞队列中的线程, 这些线程通过竞争
, 成为新的所有者。
- 刚开始 Monitor 中 Owner 为 null
- 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一个 Owner
- 在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入 EntryList BLOCKED
- Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争时是非公平的
- 图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足进入 WAITING 状态的线程
注意:
- synchronized必须是进入同一对象的monitor才有上述效果
- 不加 synchronized 的对象不会关联监视器,不遵从以上规则
2.1 synchronized原理
示例程序:
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized (lock) {
counter++;
}
}
对应字节码:
Code:
stack=2, locals=3, args_size=1
0: getstatic #2 // <- lock引用 (synchronized开始)
3: dup
4: astore_1 // lock引用 -> slot 1
5: monitorenter // 将 lock对象 MarkWord 置为 Monitor 指针
6: getstatic #3 // <- i
9: iconst_1 // 准备常数 1
10: iadd // +1
11: putstatic #3 // -> i
14: aload_1 // <- lock引用
15: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
三、结束
本文主要介绍了Java对象头以及Monitor,借此对synchronized的底层实现原理进行了说明。