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

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

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


一、对象头与Mark Word结构

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

字段 大小 说明
Mark Word 64 bits 存储锁信息、哈希码、GC年龄等
Klass Pointer 32 bits 指向类元数据
Array Length 32 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,线程进入阻塞队列,由操作系统调度。

锁升级示例

java 复制代码
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不一致,则允许重新偏向新线程。
示例代码
java 复制代码
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依赖:

xml 复制代码
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.17</version>
</dependency>

验证代码:

java 复制代码
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,压缩指针):

plaintext 复制代码
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. 锁状态变化观测

java 复制代码
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对象的"内在世界"!


参考资料:

相关推荐
多敲代码防脱发1 小时前
导出导入Excel文件(详解-基于EasyExcel)
java·开发语言·jvm·数据库·mysql·excel
黄雪超5 小时前
JVM——方法内联之去虚化
java·开发语言·jvm
xinxiyinhe6 小时前
内存泄漏与OOM崩溃根治方案:JVM与原生内存池差异化排查手册
java·开发语言·jvm
PgSheep8 小时前
深入理解 JVM:StackOverFlow、OOM 与 GC overhead limit exceeded 的本质剖析及 Stack 与 Heap 的差异
jvm·面试
意倾城10 小时前
JVM 如何优化 31 * x 为 (x << 5) - x?
java·jvm
LUCIAZZZ20 小时前
JVM之内存管理(一)
java·jvm·spring·操作系统·springboot
小王努力学编程1 天前
高并发内存池(三):TLS无锁访问以及Central Cache结构设计
jvm·数据结构·c++·学习
星星点点洲1 天前
JVM对象分配与程序崩溃排查
jvm
辛普森Mmmm1 天前
JVM详解
jvm