JVM对象头中的锁信息机制详解

JVM对象头中的锁信息机制详解

Java中的对象锁机制是高性能并发的基石,而这一切的底层实现都离不开对象头中的 Mark Word 字段。本文将系统梳理JVM对象头中锁信息的存储与演化机制,解析锁升级与批量重偏向优化原理,并通过JOL工具实战验证对象头的实际变化。


一、对象头与Mark Word结构

每个Java对象在内存中的布局如下(64位JVM,开启指针压缩):

字段大小说明
Mark Word64 bits存储锁信息、哈希码、GC年龄等
Klass Pointer32 bits指向类元数据
Array Length32 bits仅数组对象,记录长度

Mark Word的动态结构

Mark Word 的内容根据锁状态动态变化,主要分为以下几种:

状态标志位存储内容(64位)适用场景
无锁01哈希码31bit、GC年龄4bit、未用位、偏向位无竞争
偏向锁01线程ID54bit、epoch2bit、GC年龄4bit、偏向位单线程独占
轻量级锁00指向栈中锁记录的指针62bit低并发、短同步块
重量级锁10指向Monitor(OS互斥量)指针62bit高并发、长耗时临界区
GC标记11空值,用于GC标记GC期间

二、锁状态的升级与降级机制

JVM采用不可逆锁升级策略,根据竞争强度动态提升锁级别,避免频繁的锁降级带来的性能问题。

首次线程访问
其他线程竞争
自旋失败
批量重偏向
批量撤销
锁释放无竞争
锁释放
无锁
偏向锁
轻量级锁
重量级锁

各锁状态简析

  • 偏向锁:第一次线程访问时,Mark Word“贴标签”记录线程ID,后续该线程进入同步块无需CAS,极致优化无竞争场景。
  • 轻量级锁:当有第二个线程竞争时,通过CAS将Mark Word复制到线程栈中的锁记录(Lock Record),自旋尝试获取锁。
  • 重量级锁:自旋失败或竞争激烈时,升级为重量级锁,对象头指向Monitor,线程进入阻塞队列,由操作系统调度。

锁升级示例

public class Counter {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized(lock) {
            count++;
        }
    }

    public static void main(String[] args) {
        Counter counter = new Counter();
        // 1. 单线程独占(偏向锁)
        IntStream.range(0, 100).forEach(i -> counter.increment());

        // 2. 两线程交替访问(轻量级锁)
        new Thread(() -> IntStream.range(0, 100).forEach(i -> counter.increment())).start();
        new Thread(() -> IntStream.range(0, 100).forEach(i -> counter.increment())).start();

        // 3. 多线程竞争(重量级锁)
        ExecutorService pool = Executors.newFixedThreadPool(10);
        IntStream.range(0, 1000).forEach(i -> pool.submit(counter::increment));
    }
}

三、偏向锁批量重偏向机制(epoch优化)

1. 触发条件

  • 某个类的对象被不同线程撤销偏向锁超过阈值(默认20次,可用 -XX:BiasedLockingBulkRebiasThreshold 调整)。
  • 达到更高阈值(默认40次,-XX:BiasedLockingBulkRevokeThreshold)后,彻底禁用该类偏向锁

2. epoch机制实现

  • 每个类维护一个全局epoch值,对象头Mark Word中记录当前epoch副本。
  • 发生批量重偏向时,类的epoch+1,无需遍历对象头一一CAS,只需访问时对比epoch。
  • 访问对象时,若对象头epoch与类epoch不一致,则允许重新偏向新线程。
示例代码
public class BulkRebiasDemo {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        // 阶段1:A线程创建并偏向100个对象
        for (int i = 0; i < 100; i++) {
            Object obj = new Object();
            synchronized(obj) {} // 偏向线程A
            list.add(obj);
        }
        // 阶段2:B线程访问这些对象
        new Thread(() -> {
            for (Object obj : list) {
                synchronized(obj) {
                    // 前20次撤销,后续批量重偏向
                }
            }
        }).start();
    }
}

3. 优势

  • 批量操作:O(N)次CAS变为O(1)全局epoch更新,大幅降低锁撤销开销。
  • 性能提升:高并发下减少CPU颠簸,提升吞吐量。

四、JVM对象头结构与验证

1. JOL工具实战

JOL (Java Object Layout) 可直接查看对象头布局:

Maven依赖:

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

验证代码:

public static void main(String[] args) {
    Object obj = new Object();
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());

    int[] arr = new int[3];
    System.out.println(ClassLayout.parseInstance(arr).toPrintable());
}

输出示例(64位JVM,压缩指针):

java.lang.Object object internals:
 OFFSET  SIZE   TYPE DESCRIPTION
      0     4        (object header)    # Mark Word
      4     4        (object header)    # 类型指针
      8     4        (object header)    # 数组长度(数组对象)
     12     4        (loss due to...)

[I object internals:
 OFFSET  SIZE   TYPE DESCRIPTION
      0     4        (object header)    # Mark Word
      4     4        (object header)    # 类型指针
      8     4        (object header)    # 数组长度=3
     12    12    int [I.<elements>      # 数组元素
     24     4        (loss due to...)

2. 锁状态变化观测

Object lock = new Object();
System.out.println("初始状态:" + ClassLayout.parseInstance(lock).toPrintable());

synchronized(lock) {
    System.out.println("加锁状态:" + ClassLayout.parseInstance(lock).toPrintable());
}
  • 关注Mark Word最后2~3位:01(无锁/偏向锁)、00(轻量级锁)、10(重量级锁)

五、性能影响与优化建议

锁类型适用场景性能特点优化建议
偏向锁单线程独占零额外开销-XX:BiasedLockingStartupDelay=0
轻量级锁低并发短同步块自旋消耗CPU控制自旋次数(-XX:PreBlockSpin)
重量级锁高并发/长临界区上下文切换开销大减少同步块粒度
批量重偏向高并发多线程访问O(1)批量优化,减少CAS合理设置阈值,避免频繁撤销

六、总结

  • 对象头中的Mark Word是Java对象锁实现的核心,能够动态存储锁状态、线程ID、指针等信息。
  • 锁升级不可逆,保证了线程安全和性能的平衡。
  • 批量重偏向机制通过epoch优化,将大量CAS操作降为常数级epoch对比,极大提升高并发下的锁撤销效率。
  • 借助JOL工具,开发者可直观观测对象头在不同锁状态下的变化,为并发调优提供有力支撑。

理解JVM对象头中的锁机制,有助于我们写出更高效、更具可预测性的并发代码。欢迎大家用JOL实战,深度探索Java对象的“内在世界”!


参考资料:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北漂老男人

防秃基金【靠你的打赏续命】

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值