Object header 中的 Mark Word 与 klass pointer
Object header 俗称对象头,这个直译并不直观,我的理解是它是一个对象的描述加索引,其中包含了如下信息
- Mark Word
- identityhashcode 未被覆写过的hashcode _
- age 分代年龄
- biasedlock 偏向模式_
- lock 标记位
- 00~10 锁标记位
- 11 GC 标记
- klass pointer 元数据信息指针
元信息通常是与方法区关联,通常用来保存装载的类信息,常量,静态变量,即时编译器编译后的代码等数据_
在 64 位的 JVM 环境里对象头占 128bit,其中 MarkWord和 klass pointer 各占 64 bit
这个可以使用 jol (implementation "org.openjdk.jol:jol-core:0.14")验证下,例如我有如下类(先要关闭指针压缩:-XX:-UseCompressedOops )
class Calculate {private final Calculate lock = this;private int num = 10;public synchronized void add(int plusNum) {try {Thread.sleep(5 * 1000);} catch (InterruptedException e) {e.printStackTrace();}num += plusNum;}public void multi(int minNum) {synchronized (lock) {num *= minNum;}}public int getNum() {return num;}}public class SynchronizedMain {static ExecutorService executor = Executors.newFixedThreadPool(2);public static void main(String[] args) {final Calculate calculate = new Calculate();System.out.println(ClassLayout.parseInstance(calculate).toPrintable());}}
26 行的输出如下图,可以看到 object header 是一共占了 128bit 的
Mark Word
Mark Word 在大致分为三种状态 无锁、偏向锁、轻量级锁&重量级锁、空。查看 jdk8 markOop.hpp 源码的注释,可以看到关于 MarkWord 的解释
// jdk 8 markOop.hpp 部分注释// 64 bits:// --------// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
PS:说是在 jdk15 markWord.hpp 中,已经把偏向锁的相关代码删除了,但是我看注释还是有的,而且鉴于现在大多数都是 jdk8 的情况,所以还是会把偏向锁讲一下
下面的表格列举了 MarkWord 在不同状态时的占位情况,其中轻量级锁和重量级锁侵占了很多字段,这个状态可以通过 jdk8 markOop.hpp 源码注释查看到缘由
| Mark Word 64bit | 状态 | |||||
|---|---|---|---|---|---|---|
| 空:25 | hash:31 | 空:1 | GC 年龄:4 | 是否偏向:1 | 锁标记:2 (01) | 无锁 |
| 线程 ID:54 | epoch:2 | 空:1 | GC 年龄:4 | 是否偏向:1 | 锁标记:2 (01) | 偏向锁 |
| 栈中锁记录的指针:62 | 锁标记:2 (00) | 轻量级锁 | ||||
| 重量级锁的指针:62 | 锁标记:2 (10) | 重量级锁 | ||||
| 空:62 | 锁标记:2 (11) | GC 标记 |
下面就按个的去了解和验证一下
无锁状态
通过无锁状态,我可以学习一下 jol 的使用以及对 Object header 的分析
| Mark Word 64bit | 状态 | |||||
|---|---|---|---|---|---|---|
| 空:25 | hash:31 | 空:1 | GC 年龄:4 | 是否偏向:1 | 锁标记:2 (01) | 无锁 |
首先将如下代码运行下,看下输出(Calculate类 在前面展示过)
final Calculate calculate = new Calculate();System.out.println(calculate.hashCode());System.out.println(ClassLayout.parseInstance(calculate).toPrintable());
输出的结果如下
把前 object header 中的前 64 位也就是 MarkWord 删选出来如下
00000001 00100010 11101001 01010010 01111000 00000000 00000000 00000000
由于是小端排序,所以要将二进制反过来看
颜色指示:空、hash 值、GC 年龄、是否偏向、锁标记
小端排序: 00000001 00100010 11101001 01010010 01111000 00000000 00000000 00000000
大端排序: 00000000 00000000 00000000 01111000 01010010 11101001 00100010 00000001
可以看到紫色的 hash 值计算出来跟打印的是一致的,锁标记和偏向也都是正确的
偏向锁状态&匿名偏向锁状态
如果不了解偏向锁,可以看一下这篇博文
语雀内容
| Mark Word 64bit | 状态 | |||||
|---|---|---|---|---|---|---|
| 线程 ID:54 | epoch:2 | 空:1 | GC 年龄:4 | 是否偏向:1 | 锁标记:2 (01) | 偏向锁 |
下面还是通过代码和 jol 的输出来验证下:
Thread.sleep(5000);final Calculate calculate = new Calculate();System.out.println(ClassLayout.parseInstance(calculate).toPrintable());synchronized (calculate){System.out.println(ClassLayout.parseInstance(calculate).toPrintable());}
上述代码中,停顿 5s 是为了等待偏向锁的开启,那么输出入下:
可以看到,在进入 synchronized 块以前,calculate 实例的对象头处于匿名偏向锁状态,而进入了同步模块后则升级为偏向锁
轻量级锁状态
首先看下轻量级锁的定义
语雀内容
| Mark Word 64bit | 状态 | |||||
|---|---|---|---|---|---|---|
| 栈中锁记录的指针:62 | 锁标记:2 (00) | 轻量级锁 |
还是测试代码加分析
Thread.sleep(5000);final Calculate calculate = new Calculate();synchronized (calculate){System.out.println(ClassLayout.parseInstance(calculate).toPrintable());}new Thread(() -> {synchronized (calculate){System.out.println(ClassLayout.parseInstance(calculate).toPrintable());}}).start();
jol 输出的结果如下
第一个输出是偏向锁,偏向的是 main 线程
在第二个线程里,又访问了锁对象,所以对象就升级为了轻量级锁
重量级锁状态
同样的去看下重量锁的定义
语雀内容
**
| Mark Word 64bit | 状态 | |||||
|---|---|---|---|---|---|---|
| 重量级锁的指针:62 | 锁标记:2 (10) | 重量级锁 |
依然是代码和分析
Thread.sleep(5000);final Calculate calculate = new Calculate();synchronized (calculate) {System.out.println(ClassLayout.parseInstance(calculate).toPrintable());}Thread thread1 = new Thread(() -> {synchronized (calculate) {System.out.println("当前线程为: "+Thread.currentThread().getName());System.out.println(ClassLayout.parseInstance(calculate).toPrintable());}});thread1.setName("thread1");Thread thread2 = new Thread(() -> {try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (calculate) {System.out.println("当前线程为: "+Thread.currentThread().getName());System.out.println(ClassLayout.parseInstance(calculate).toPrintable());}});thread2.setName("thread2");thread1.start();thread2.start();
这个我在我的电脑上不是每次都是必现的,多运行几次,就会出现轻量级锁升级为重量级锁的情况,如下
上边的输出就验证了锁升级过程,偏向->轻量级->重量级
