欢迎光临
我们一直在努力

如何利用 JOL 工具类观察 Java 对象头布局及偏向锁的撤销细节

Java 对象在 JVM 内存中有着固定的布局,通常由三部分组成:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。其中,对象头包含了至关重要的运行时元数据,特别是 Mark Word(标记字段),它记录了对象的哈希码、GC 年龄、锁状态等信息。

Java Object Layout (JOL) 是一个强大的工具,可以用来分析和可视化 Java 对象的内存布局,这对于理解 JVM 内部机制,特别是锁优化(如偏向锁)的原理至关重要。

1. JOL 引入

要使用 JOL,你需要在项目中引入 jol-core 依赖(以 Maven 为例):

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.17</version>
</dependency>

2. 观察对象头布局

我们首先观察一个简单对象的结构。在 64 位 JVM 且开启指针压缩(默认)的情况下,对象头通常占用 12 字节(Mark Word 8 字节,Class Pointer 4 字节)。

import org.openjdk.jol.info.ClassLayout;

public class JolObjectDemo {
    public static void main(String[] args) {
        Object o = new Object();
        // 打印对象o的内存布局
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }
}

输出(简化版):

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next field being aligne...)
Instance size: 16 bytes

前 8 字节是 Mark Word。注意 Mark Word 的最后三位:…001 表示对象处于无锁状态(且可偏向)。

3. 偏向锁的获取与观察

偏向锁(Biased Locking)是 JVM 对轻量级锁的进一步优化,用于提高在单个线程重复获取同一锁时的性能。默认情况下,JVM 启动后会有一段延迟(通常 4 秒)才开始启用偏向锁。为了实验方便,我们需要设置 JVM 参数来关闭这个延迟:

JVM Arguments: -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

现在,我们观察一个对象如何被偏向:

public class BiasedLockingRevocationDemo {
    public static void main(String[] args) throws InterruptedException {
        // 确保JVM参数设置了 -XX:BiasedLockingStartupDelay=0

        Object o = new Object();
        Thread t1 = Thread.currentThread();

        System.out.println("--- 1. 初始状态 (偏向可用) ---");
        System.out.println(ClassLayout.parseInstance(o).toPrintable());

        synchronized (o) { // T1 获取锁
            System.out.println("--- 2. T1 获取偏向锁 ---");
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
            // 此时 Mark Word 最后三位应为 101,并包含 T1 的线程 ID
        }

        System.out.println("--- 3. T1 释放锁 (仍保持偏向) ---");
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }
}

Mark Word 状态变化解析:

  1. 初始状态: Mark Word 末尾为 …001…101 (可偏向,但还未偏向任何线程)。
  2. T1 获取偏向锁: Mark Word 末尾为 …101。最关键的是,Mark Word 的中间部分不再是 0,而是记录了 T1 的线程 ID(Epoch 和 Thread ID)。
  3. T1 释放锁: Mark Word 状态不变,对象依然偏向于 T1。下次 T1 再进来时,无需 CAS 操作,直接判断 Mark Word 中的线程 ID 即可。

4. 偏向锁的撤销(Revocation)

偏向锁的撤销发生在两种主要情况下:

  1. 竞争撤销: 另一个线程(T2)尝试获取已被 T1 偏向的锁。
  2. 非锁操作撤销: 比如调用对象的 hashCode()

由于哈希码需要占用 Mark Word 的空间,如果对象处于偏向锁状态,Mark Word 已经被线程 ID 占用,因此 JVM 会强制撤销偏向锁,将其升级为无锁或轻量级锁状态,并将哈希码写入。

我们使用调用 hashCode() 的方式观察撤销:

public class RevocationByHashCodeDemo {
    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();

        // T1 获取偏向锁
        synchronized (o) {
             System.out.println("--- 1. 初始获取偏向锁 ---");
             // Mark Word: ...101 (偏向 T1)
             System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }

        // 强制调用 hashCode(),哈希码会写入 Mark Word,导致偏向锁撤销
        System.out.println("对象 HashCode: " + o.hashCode());

        System.out.println("--- 2. 调用 hashCode 后的状态 ---");
        // Mark Word 末尾变为 ...000 (轻量级/重量级锁,但此时应是无锁状态且哈希码写入)
        // 或 ...001 (无锁,哈希码写入)
        System.out.println(ClassLayout.parseInstance(o).toPrintable());

        // 再次尝试加锁,此时已无法进入偏向锁状态
        synchronized (o) {
             System.out.println("--- 3. 再次加锁 (不再是偏向锁) ---");
             // 此时应升级为轻量级锁 (Mark Word 末尾 00)
             System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }
}

通过 JOL 的输出,我们可以清晰地观察到 Mark Word 从记录线程 ID 的 …101 状态,经过 hashCode() 调用后,变成记录哈希码的无锁状态(通常末尾变为 …001),并且该对象将永久不能再进入偏向锁状态,从而印证了偏向锁撤销的细节和不可逆性。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » 如何利用 JOL 工具类观察 Java 对象头布局及偏向锁的撤销细节
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址