Java对象头结构

Java对象头包含两部分:Mark WordKlass Pointer

  • Mark Word:存储对象的哈希码、GC分代年龄、锁状态标志(如偏向锁、轻量级锁、重量级锁)。32位系统占4字节,64位系统占8字节。
  • Klass Pointer:指向对象元数据的指针,JVM通过它确定对象的类信息。

锁状态变化时,Mark Word的内容会动态替换。例如:

  • 无锁状态:存储哈希码和分代年龄。
  • 偏向锁:存储偏向线程ID和epoch。
  • 轻量级锁:指向栈中锁记录的指针。
  • 重量级锁:指向互斥量(Monitor)的指针。

锁升级过程

Java锁从低到高分为无锁偏向锁轻量级锁重量级锁,升级过程不可逆。

偏向锁
适用于单线程重复访问的场景。Mark Word记录线程ID,后续加锁仅需比对ID,无需CAS操作。若发生竞争(另一个线程尝试获取锁),偏向锁撤销并升级为轻量级锁。

轻量级锁
通过CAS操作将Mark Word替换为指向线程栈中锁记录的指针。若CAS失败,表示存在竞争,膨胀为重量级锁。

重量级锁
依赖操作系统提供的互斥量(如Mutex),线程阻塞并进入内核态等待唤醒。适用于高竞争场景,但性能开销较大。

Monitor机制

重量级锁的核心是Monitor对象(也称为管程),每个Java对象关联一个Monitor。Monitor包含以下关键字段:

  • Owner:持有锁的线程。
  • EntryList:阻塞等待锁的线程队列。
  • WaitSet:调用wait()后进入等待状态的线程队列。

线程获取锁时,若Owner为空则成功占用,否则进入EntryList;释放锁时唤醒EntryList中的线程。

内存屏障与可见性

锁的实现依赖内存屏障(Memory Barrier)保证多线程下的可见性和有序性。JVM通过以下屏障类型控制指令顺序:

  • LoadLoad:确保屏障前的读操作先于屏障后的读操作。
  • StoreStore:确保屏障前的写操作先于屏障后的写操作。
  • LoadStore:确保屏障前的读操作先于屏障后的写操作。
  • StoreLoad:全能屏障,确保屏障前的所有操作先于屏障后的所有操作。

synchronizedvolatile在释放锁或写入变量时会插入StoreLoad屏障,强制刷新处理器缓存,保证其他线程能立即看到最新值。

底层汇编与CAS

轻量级锁和偏向锁依赖**CAS(Compare-And-Swap)**操作,对应汇编指令如cmpxchg(x86架构)。例如,OpenJDK的Atomic::cmpxchg函数最终调用:

lock cmpxchg [dest], expected, new_value  

lock前缀保证操作的原子性,并隐含内存屏障效果。

锁优化实践

  • 减少锁粒度:如ConcurrentHashMap分段锁。
  • 锁消除:JIT编译器检测到不可能竞争的锁会直接移除。
  • 锁粗化:合并相邻的同步块减少锁开销。
  • 自旋优化:轻量级锁竞争时,线程短暂自旋而非立即阻塞。

理解这些底层机制有助于编写高效并发代码,避免不必要的性能损耗。

更多推荐