Monitor概念
参考文章
Java对象头
对象头包括了关于堆对象的布局、类型、GC状态、同步状态和标识哈希码的基本信息。Java对象和vm内部对象都有一个共同的对象头格式。它是Java对象和虚拟机内部对象都有的共同格式,由两个字(计算机术语)组成。另外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。
Integer : 对象头(8字节) + int(4字节) = 12字节
以 32-bit JVM 为例
普通对象 :
普通对象头由两部分组成
- Mark Word : 标记字 , 32 位上是 4个字节, 64位上是 8 个字节
- Klass Word: 类指针
Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等。
Mark Word在32位JVM中的长度是32bit,在64位JVM中长度是64bit。我们打开openjdk的源码包,对应路径/openjdk/hotspot/src/share/vm/oops
Mark Word在不同的锁状态下存储的内容不同,在32位JVM中是这么存的
在64位JVM中是这么存的
虽然它们在不同位数的JVM中长度不一样,但是基本组成内容是一致的。
- 锁标志位(lock):区分锁状态,11时表示对象待GC回收状态, 只有最后2位锁标识(11)有效。
- biased_lock:是否偏向锁,由于无锁和偏向锁的锁标识都是 01,没办法区分,这里引入一位的偏向锁标识位。
- 分代年龄(age):表示对象被GC的次数,当该次数到达阈值的时候,对象就会转移到老年代。
- 对象的hashcode(hash):运行期间调用System.identityHashCode()来计算,延迟计算,并把结果赋值到这里。当对象加锁后,计算的结果31位不够表示,在偏向锁,轻量锁,重量锁,hashcode会被转移到Monitor中。
- 偏向锁的线程ID(JavaThread):偏向模式的时候,当某个线程持有对象的时候,对象这里就会被置为该线程的ID。 在后面的操作中,就无需再进行尝试获取锁的动作。
- epoch:偏向锁在CAS锁操作过程中,偏向性标识,表示对象更偏向哪个锁。
- ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。当锁获取是无竞争的时,JVM使用原子操作而不是OS互斥。这种技术称为轻量级锁定。在轻量级锁定的情况下,JVM通过CAS操作在对象的标题字中设置指向锁记录的指针。
- ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。如果两个不同的线程同时在同一个对象上竞争,则必须将轻量级锁定升级到Monitor以管理等待的线程。在重量级锁定的情况下,JVM在对象的ptr_to_heavyweight_monitor设置指向Monitor的指针。
数组对象
其中 Mark Word 结构为
64 位虚拟机 Mark Word
Monitor锁
Monitor图解
Monitor : 监视器 或者 管程
每一个Java对象都可以关联一个Monitor对象, 如果使用 synchronized给对象上锁(重量级)以后, 该对象头的MarkWord中就被指向Monitor对象的指针.
Monitor的结构如下 :
- 初始状态时, Moniotr的Owner 为null
- 当Thread-2 执行synchronized(obj) 时, 就会将Monitor的Owner设置为Thread-2, Monitor中只能有一个Owner
- 在 Thread-2 上锁的过程中, 如果其他的线程想要执行 synchronized(obj) , 就进入 EntryList , 变为 BLOCK状态
- Thread-2执行完同步代码块的内容, 然后唤醒EntryList的中等待的线程来竞争锁, 竞争锁是非公平的
- WaitSet 中是之前获得过锁, 但是不满足运行条件的, 进入WAITING状态的线程
注意 :
- synchronized必须是进入同一个对象的Monitor才有上面的效果
- 不加synchronized的对象, 不会关联Monitor锁. 不遵守以上的规则