object.hashcode()默认返回值本质是首次调用时生成并缓存在对象头mark word中的32位不可变整数,后续直接读取,不随gc移动更新,也不等于内存地址。

Java 中 Object 类的 hashCode() 方法返回值,本质上就来源于对象头中的 Mark Word —— 它不是每次调用都重新计算,而是首次调用时写入对象头,并长期复用。理解这个机制,就能把哈希码和内存布局真正串起来。
哈希码真实存储位置就在对象头的 Mark Word 里
在 HotSpot JVM 中,每个对象在堆中创建时,对象头(Header)会立即分配固定空间。64 位 JVM(开启压缩指针)下,对象头共 12 字节:8 字节 Mark Word + 4 字节 Klass Pointer。其中 Mark Word 是哈希码的“落脚点”:
- 对象刚创建、未调用
hashCode()时,Mark Word 中对应哈希码的位段通常为 0 或未初始化; - 第一次调用
hashCode(),JVM 生成一个随机数(或基于地址的扰动值),并直接写入 Mark Word 的指定区域; - 后续再调用,直接从 Mark Word 读取,不重复计算 —— 所以哈希码与对象生命周期绑定,且与内存地址间接相关,但不等于地址本身。
哈希码写入受锁状态影响,存在复用与覆盖
Mark Word 只有 8 字节(64 位),却要承载哈希码、分代年龄、锁标志、偏向线程 ID 等多种信息。它们是“动态复用”的,不是各自独占字段:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 无锁态(标志位 01)下,Mark Word 低 32 位存哈希码,高 32 位存分代年龄等;
- 一旦对象进入偏向锁或轻量级锁状态,原有哈希码空间会被锁记录指针或线程 ID 覆盖;
- 此时若再调用
hashCode(),JVM 必须“膨胀”锁(升级为重量级锁),把原哈希码迁移保存到 Monitor 对象中,再更新 Mark Word 指向它 —— 这就是为什么“已加锁对象调用 hashCode() 可能触发锁升级”。
验证哈希码与对象头关联的典型现象
几个常见行为背后都是对象头在起作用:
-
同一对象多次调用
hashCode()结果不变:因为始终读的是 Mark Word 中已写入的值; -
子类重写
hashCode()后不再使用对象头哈希:JVM 会跳过 Mark Word,直接执行你的逻辑,此时对象头里的哈希位可能保持为 0 或无效; - 对象被 GC 移动后哈希码不变:因为 Mark Word 随对象一起复制到新内存地址,哈希值作为元数据被保留;
- 空 Object 实例占 16 字节(含对齐填充):对象头 12 字节 + 实例数据 0 字节 + 4 字节对齐填充 → 正是因为头结构固定,才能确保哈希码有稳定存储槽位。
开发中可借助工具观察对象头与哈希码关系
想亲眼看到哈希码如何落进对象头,可用 JDK 自带的 Unsafe 或第三方库(如 JOL):
- 用
new Object().hashCode()获取值,再用 JOL 的ClassLayout.parseInstance(obj).toPrintable()查看内存布局; - 对比加锁前后的布局输出,能看到 Mark Word 内容变化(比如从含哈希码变为含锁指针);
- 注意:JDK 9+ 默认关闭了
-XX:+UseCompressedOops相关调试支持,需搭配-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly或使用 JOL 1.16+ 版本。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










