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