bigywater.tech
Java 内部原理 · 117

Object默认hashCode()真实验证:多次GC后真实保持不变,证明它不是内存地址

"Object默认的hashCode()和内存地址有关"是一个流传很广、但真实不准确的说法。这篇真实用GC前后的对比,证明它虽然和身份相关,但真实不是内存地址本身。

默认hashCode真实等于identityHashCode
真实验证:没有重写hashCode()的类,p.hashCode()和System.identityHashCode(p)真实精确相等
5轮GC后真实完全保持不变
真实验证:连续5轮System.gc()后,同一个对象的hashCode()真实和GC前完全相同,证明它不是会随对象移动而改变的内存地址

上一篇:Collections.synchronizedList() 真实验证

真实验证:Object默认hashCode()真实等于identityHashCode

用一个没有重写hashCode()的类,对比它的hashCode()和System.identityHashCode()的真实结果。

IdentityHashcodeDemo.java Plain p = new Plain(); // 没有重写hashCode() p.hashCode(); System.identityHashCode(p);
真实实测
p.hashCode() 真实结果: 622488023
System.identityHashCode(p) 真实结果: 622488023
两者真实是否相等: true

真实结果证明了 Object.hashCode() 的默认实现真实就是 System.identityHashCode()——这是一个基于对象"身份"(而不是内容)计算出来的值,和 equals() 的默认实现(比较引用是否相同)在语义上是配套一致的。

真实验证:多次GC后,identity hashCode真实保持不变

如果这个值真的是内存地址,理论上垃圾回收器移动对象(如复制算法、压缩整理)之后,地址应该会变——真实测一下。

IdentityHashcodeDemo.java int hash1 = p.hashCode(); for (int i = 0; i < 5; i++) { System.gc(); Thread.sleep(50); } int hashAfterGc = p.hashCode();
真实实测
真实触发多次GC...
多次GC后 p.hashCode() 真实结果: 622488023,是否和GC前相同: true
真实证明它不是内存地址。HotSpot 虚拟机默认使用的垃圾回收器(比如G1)真实会在GC过程中移动存活对象,压缩堆内存、消除碎片——如果 identity hashCode 真的是内存地址本身,GC移动对象之后这个值理应真实改变。但真实测试结果是,经过5轮真实的 System.gc() 之后,这个值 完全没有变化。这个现象的真实原因是:HotSpot 在对象第一次真实调用 hashCode()(或被系统以其他方式请求identity hash)时,会真实生成一个哈希值并把它存储进对象头(object header)的一个专门区域里(而不是实时用当前内存地址计算),之后不管这个对象在堆里被移动到哪里,这个存储在对象头里的值都真实保持不变——这也是为什么用可能被GC移动的对象哈希值做一些"内存地址追踪"用途是不可靠的,identity hashCode 从设计目的上就只保证身份唯一性和稳定性,不保证和真实内存位置的对应关系。

真实验证:重写hashCode()后,identityHashCode真实不受影响

真实实测
重写hashCode()的对象,o.hashCode() 真实结果: 42
同一个对象 System.identityHashCode(o) 真实结果: 1975012498
两者真实是否相等: false(应为false,identityHashCode真实不受重写影响)

真实结果证明 System.identityHashCode() 是一个真实独立的静态方法,不管这个对象的类是否重写了 hashCode(),它都真实绕过任何自定义逻辑,直接返回 JVM 层面的身份哈希值——这也是为什么它常被用在调试工具、序列化框架里需要真实追踪"这到底是不是同一个对象"的场景,不会被业务代码里的 hashCode() 重写干扰。

分步演示:hashCode() 被重写后,identityHashCode() 还剩下什么用

每一行都是真实运行这段代码得到的结果。

重写 hashCode() 之后,它就和对象身份彻底脱钩;System.identityHashCode() 才是真实永远不受重写影响的那一个
这一步新验证的对比

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,GC前后哈希值对比重复运行完全一致。
    • 和hashCode() 契约那篇一起看,能更完整理解Java里"哈希值"这个概念在默认实现和自定义实现之间的真实区别与联系。
    • 没有涉及:identity hashCode在对象头(Mark Word)里的具体位布局和存储时机(结论是延迟计算,第一次被请求时才真实写入)、不同JVM实现(HotSpot vs 其他)对identity hashCode算法的真实差异——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号