揭秘Java对象头与锁升级机制
·
Java对象头结构
Java对象头包含两部分:Mark Word和Klass 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:全能屏障,确保屏障前的所有操作先于屏障后的所有操作。
synchronized和volatile在释放锁或写入变量时会插入StoreLoad屏障,强制刷新处理器缓存,保证其他线程能立即看到最新值。
底层汇编与CAS
轻量级锁和偏向锁依赖**CAS(Compare-And-Swap)**操作,对应汇编指令如cmpxchg(x86架构)。例如,OpenJDK的Atomic::cmpxchg函数最终调用:
lock cmpxchg [dest], expected, new_value
lock前缀保证操作的原子性,并隐含内存屏障效果。
锁优化实践
- 减少锁粒度:如
ConcurrentHashMap分段锁。 - 锁消除:JIT编译器检测到不可能竞争的锁会直接移除。
- 锁粗化:合并相邻的同步块减少锁开销。
- 自旋优化:轻量级锁竞争时,线程短暂自旋而非立即阻塞。
理解这些底层机制有助于编写高效并发代码,避免不必要的性能损耗。
更多推荐


所有评论(0)