object.hashcode()默认实现与对象内存地址无关:hotspot中采用惰性生成的identity hash code,基于全局计数器或线程随机数,固定后存于对象头,不随gc移动而变化。

Object.hashCode() 默认实现和对象地址无关
Java 的 Object.hashCode() 默认实现(即未重写时)在 HotSpot JVM 中**不返回对象的内存地址**,哪怕是在 64 位 JVM 上。这是很多人从“哈希码应该唯一标识对象”出发产生的误解——但 JVM 明确要求 hashCode() 只需满足合同(如相等对象必须有相同哈希码),并不承诺可逆、不冲突或与地址绑定。
HotSpot 中默认 hashCode 的生成逻辑
HotSpot 使用一种叫“identity hash code”的机制,其值在对象首次调用 hashCode() 时惰性生成,并存储在对象头(mark word)中。具体策略取决于 JVM 启动参数和运行时状态:
- 默认(
-XX:+UseBiasedLocking开启时):使用一个全局原子计数器(hashCode字段初始为 0,首次调用时取当前计数值并自增) - 禁用偏向锁(
-XX:-UseBiasedLocking)或某些 GC 场景下:可能基于线程局部随机数(os::random())生成,再经简单扰动 - 该值一旦生成就固定,后续调用直接返回缓存值,与对象是否移动(如 G1/CMS 的 GC 搬移)完全无关
这意味着:同一个对象在不同 JVM 实例中、甚至同一 JVM 不同次运行中,hashCode() 值都可能不同;而真实物理地址(如通过 Unsafe.getAddress() 获取的)每次创建对象都变,且 GC 后必然变。
为什么不能把 hashCode 当作地址用?
直接把 hashCode() 当地址会导致严重问题:
-
地址空间错位:64 位地址是 64bit,而
hashCode()是int(32bit 有符号),高位信息直接丢失 -
GC 不安全:ZGC/Shenandoah 等支持并发搬移的 GC 会随时移动对象,但
hashCode()值不变 —— 若你拿它当地址去读内存,要么读到旧位置(已释放/覆写),要么越界 -
调试误导:用
jmap -histo或 JOL(Java Object Layout)看对象布局时,显示的 “address” 是 GC 视角的起始地址(如 0x0000000800010000),而System.identityHashCode(obj)返回的可能是 182374912,二者数值上毫无映射关系
想真正获取对象运行时地址,得用 Unsafe + objectFieldOffset 配合 getLong(),但这是非标准、不可移植、且仅限调试的危险操作。
验证方式:用 JOL 和 System.identityHashCode 对比
最直观的反证就是实测:
import org.openjdk.jol.vm.VM;
import org.openjdk.jol.info.ClassLayout;
import java.util.*;
public class HashCodeVsAddress {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
System.out.println("a identityHashCode: " + System.identityHashCode(a));
System.out.println("b identityHashCode: " + System.identityHashCode(b));
System.out.println("VM address bits: " + VM.current().addressSize());
System.out.println("JOL layout:\n" + ClassLayout.parseInstance(a).toPrintable());
}
}
输出中你会看到:identityHashCode 是两个接近但不连续的整数(比如 123456789 和 123456790),而 JOL 显示的地址类似 0x0000000800012340 —— 两者既不线性相关,也不模 2³² 同余。这种差异不是偶然,是 HotSpot 主动设计的结果。
真正要注意的是:任何依赖 hashCode() 表示位置、做指针运算、或跨进程传递的逻辑,都会在 64 位 JVM 上崩得无声无息。










