跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

什么是Java中的对象头结构(Mark Word)_锁标志位与哈希值的存储分布

Mark Word 动态复用同一内存区域,依对象状态切换存储哈希值、锁信息、GC年龄等;轻量级锁会覆盖哈希值,导致hashCode()结果变化,引发并发容器异常。 Mark Word 里到底存了啥 Java 对象头中的
Mark Word
是个 64 位(64 位 JVM)或 32 位(32 位 JVM)的紧凑字段,它不是固定存某一种东西,而是**动态复用同一块内存**,根据对象所处状态切换用途。你查到的“哈希值”“锁标志位”“GC 分代年龄”等,全挤在这一个字长里,靠低位的几个比特(
lock bits
)来区分当前模式。 哈希值和锁标志位为什么会冲突 调用过
System.identityHashCode()
Object.hashCode()
(未被重写)的对象,JVM 会在首次计算时把哈希值塞进
Mark Word
;但一旦对象被加锁(比如进入
synchronized
块),JVM 就得腾地方存锁记录指针、线程 ID、epoch 等——哈希值就得被挤走,甚至丢弃(轻量级锁升级后可能不再恢复)。这不是 bug,是设计取舍:空间比“永远保留哈希”更重要。 没锁且没算过哈希:低位 2–3 bit 是
01
(无锁态),高位存分代年龄或偏向线程 ID 算过哈希但没锁:低位是
01
,高位存哈希值(31 位或 61 位,取决于 JVM 位数) 轻量级锁:低位变成
00
,原哈希值被覆盖,指向栈中
Lock Record
偏向锁:低位是
01
,但含义变了,高位存偏向线程 ID + epoch,哈希值已不可恢复 为什么
hashCode()
调用后又加锁,结果可能变 这是最常踩的坑:你以为对象哈希值是稳定的,但只要它后续被加锁(哪怕只是 synchronized 方法里临时一进),再读
hashCode()
可能返回新值(JVM 在哈希丢失后会重新生成一个,通常是基于地址再散列)。尤其在并发容器里用自定义 key 时,如果 key 的
hashCode()
依赖对象身份,又在多线程里被同步访问,就可能触发
HashMap
键错位、
ConcurrentHashMap
扩容异常等隐性问题。 避免在会被加锁的对象上依赖
System.identityHashCode()
做业务逻辑(比如分片、路由) 若必须稳定哈希,自己缓存到 final 字段:
private final int cachedHash = System.identityHashCode(this);
JDK 17+ 的
-XX:+UseCompactObjectHeaders
会进一步压缩
Mark Word
,加剧哈希与锁的互斥,别盲目开启 怎么验证当前对象的 Mark Word 状态 没有标准 API 直接读
Mark Word
,但可以用
Unsafe
配合对象地址粗略窥探(仅限调试,生产禁用): AI Word 一款强大的 AI 智能内容创作平台,致力于帮助用户高效生成高质量、原创且符合 SEO 规范的各类文章。 下载 立即学习 “ Java免费学习笔记(深入) ”;
Unsafe unsafe = Unsafe.getUnsafe(); long offset = unsafe.objectFieldOffset(Object.class.getDeclaredField("hashCode")); // 实际需通过内存地址 + header size 计算,更可靠的方式是用 JOL(Java Object Layout)工具
更实用的办法是用
jol-cli
工具:
java -jar jol-cli.jar internals java.lang.Object
,它会显示不同锁状态下
Mark Word
的二进制布局。注意:输出受 JVM 参数影响极大,
-XX:+UseBiasedLocking
开关会直接改变低位编码规则。 真正难的不是看懂结构,而是意识到:同一个字段,在不同时间点、不同线程操作下,语义完全不同。你写的代码如果隐式依赖它的某个快照状态(比如以为哈希一直存在),运行时就可能悄无声息地出错。

相关文章