Java对象头与Monitor监视器
# Java对象头与Monitor监视器
# Java对象头
普通对象的对象头
32 位虚拟机 Mark Word 占 32 位,也就是 4 个字节;64 位虚拟机 Mark Word 占 64 位,也就是 8 个字节;
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
2
3
4
5
数组对象的对象头
|---------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|------------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|------------------------|
2
3
4
5
# Mark Word
这部分用来存储对象自身的运行时数据,如 hashcode、gc 分代年龄。
Mark word
的位长度为 JVM 的一个 Word 大小,也就是说 32 位 JVM 的Mark word
为32位,64 位 JVM 的Mark word
为 64 位。
32 位对象头
|-------------------------------------------------------|--------------------|
| Mark Word (32 bits) | State |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30 | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
| | lock:2 | Marked for GC |
|-------------------------------------------------------|--------------------|
2
3
4
5
6
7
8
9
10
11
12
13
64 位对象头
|---------------------------------------------------------------------------------------|--------------------|
| Mark Word (64 bits) | State |
|---------------------------------------------------------------------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 (0) | lock:2 (01) | Normal |
|---------------------------------------------------------------------------------------|--------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 (1) | lock:2 (01) | Biased |
|---------------------------------------------------------------------------------------|--------------------|
| ptr_to_lock_record:62 | lock:2 (00) | Lightweight Locked |
|---------------------------------------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:62 | lock:2 (10) | Heavyweight Locked |
|---------------------------------------------------------------------------------------|--------------------|
| | lock:2 (11) | Marked for GC |
|---------------------------------------------------------------------------------------|--------------------|
2
3
4
5
6
7
8
9
10
11
12
13
各部分含义
biased_lock | lock | 状态 |
---|---|---|
0 | 01 | 无锁 |
1 | 01 | 偏向锁 |
0 | 00 | 轻量级锁 |
0 | 10 | 重量级锁 |
0 | 11 | GC标准 |
- biased_lock:对象是否启用偏向锁标记,只占 1 个二进制位。为 1 时表示对象启用偏向锁,为 0 时表示对象没有偏向锁
- lock:2 位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了 lock 标记。该标记的值不同,整个mark word 表示的含义不同;
- age:4 位的 Java 对象年龄。在垃圾回收过程中,对象在 Survivor 区复制一次,年龄增加 1。当对象达到设定的阈值时,将会晋升到老年代,年龄默认阈值为 15。由于 age 只有 4 位,所以最大值为 15,这就是
-XX:MaxTenuringThreshold
选项最大值为 15 的原因 - identity_hashcode:25 位的对象标识 Hash 码,采用延迟加载技术。调用方法
System.identityHashCode()
计算(本地方法),并会将结果写到该对象头中。当对象被锁定时,该值会移动到管程 Monitor 中 - thread:持有偏向锁的线程 ID
- epoch:偏向时间戳
- ptr_to_lock_record:指向栈中锁记录的指针
- ptr_to_heavyweight_monitor:指向管程 Monitor 的指针
# Klass Word
这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM 通过这个指针确定对象是哪个类的实例。该指针的位长度为 JVM 的一个字大小,即 32 位的 JVM 为 32 位,64 位的 JVM 为 64位。
如果应用的对象过多,使用 64 位的指针将浪费大量内存,统计而言,64 位的 JVM 将会比 32 位的 JVM 多耗费 50% 的内存。为了节约内存可以使用选项 +UseCompressedOops
开启指针压缩,其中,oop 即 ordinary object pointer 普通对象指针。开启该选项后,下列指针将压缩至 32 位:
每个Class的属性指针(即静态变量)
每个对象的属性指针(即对象变量)
普通对象数组的每个元素指针
当然,也不是所有的指针都会压缩,一些特殊类型的指针 JVM 不会优化,比如指向 PermGen 的 Class 对象指针(JDK8 中指向元空间的 Class 对象指针)、本地变量、堆栈元素、入参、返回值和 NULL 指针等。
# array length
如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着 JVM 架构的不同而不同:32 位的JVM上,长度为 32 位;64 位 JVM 则为 64 位。64 位 JVM 如果开启 +UseCompressedOops
选项,该区域长度也将由 64 位压缩至 32 位。
# Monitor
Monitor被翻译为监视器或管程,可以理解为一个同步工具或机制,每一个 Java 对象都有一把看不见的锁,称为内部锁或 Monitor 对象。Monitor 是线程私有的数据结构,每一个线程都有一个可用 monitor record 列表,同时还有一个全局的可用列表。
每一个被锁住(重量级锁)的对象都会和一个 monitor 关联,该对象头的Mark Word中就被设置指向Monitor对象的指针,同时 monitor 中有一个 Owner 字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。可以通过 synchronized 关键字来获取该对象的 monitor。当一个线程获取了一个对象的 monitor 后,其他线程就无法再获取该对象的 monitor,直到该线程释放该对象的 monitor。这样就可以保证同一时刻只有一个线程能够访问该对象的关键代码段,从而避免了多线程并发访问的问题。
Monitor结构如下
obj 是 java 对象,monitor 是操作系统提供的。obj 的 MarkWord 部分有指针指向 Monitor。
- 刚开始 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 的对象不会关联监视器,不遵从以上规则