JVM 如何确保 System.identityHashCode() 永远不会改变?

Posted

技术标签:

【中文标题】JVM 如何确保 System.identityHashCode() 永远不会改变?【英文标题】:How does the JVM ensure that System.identityHashCode() will never change? 【发布时间】:2010-11-06 23:27:35 【问题描述】:

Object.hashCode() 的默认实现通常是对象在内存中的分配地址的某些函数(尽管 JLS 没有强制要求)。鉴于 VM 在内存中分流对象,为什么 System.identityHashCode() 返回的值在对象的生命周期内从不改变?

如果是“一次性”计算(对象的hashCode 计算一次并隐藏在对象标题或其他东西中),那么这是否意味着两个对象有可能具有相同的identityHashCode(如果它们恰好首先分配在内存中的相同地址)?

【问题讨论】:

相关问题:该内存地址是真实的内存地址还是虚拟的东西,即使对象被洗牌也可以保持固定?如果是虚拟的,那就太好了,因为不需要调整指向它的指针。另一方面,这意味着额外的间接性和潜在的大映射表。 第一次请求时,地址略有重新排列。 (返回低位全为零的哈希码并不好。) 其实哪里说identityHashCode不能改变? System.identityHashCode 的 JavaDoc 对此并不清楚。 当然,如果 identityHashCode 确实发生了变化,你只能使用实现 hashCode() 的对象作为哈希表中的键。 好的,明白了:“在 Java 应用程序的执行过程中,只要在同一个对象上多次调用 (hashCode),hashCode 方法必须始终返回相同的整数,前提是没有使用修改了对象上的等于比较。”在这种情况下,equals 是对象身份比较。 【参考方案1】:

现代 JVM 将值保存在对象标头中。我相信该值通常仅在首次使用时计算,以便将对象分配所花费的时间保持在最低限度(有时低至十几个周期)。可以编译通用的 Sun JVM,使所有对象的标识哈希码始终为 1。

多个对象可以具有相同的身份哈希码。这就是哈希码的本质。

【讨论】:

对 - 我刚刚查看了 synchronizer.cpp(vm 运行时源代码)中的 ObjectSynchronizer::FastHashCode,在生成哈希码后,它看起来像是将其合并到对象头中。看起来HashCode有几种可能的实现;您提到的为所有对象返回 1 的那个用于确保 VM 的任何部分都不会因为任何原因假定哈希码是唯一的。 public static native int identityHashCode(Object x);是本机方法。你能从本机实现代码的角度来解释它吗?我的意思是 C++ 实现。它主要用于 inIdentityHashMap 对吧? @Tom 对象头是什么意思?您还写道“我相信该值通常仅在首次使用时计算,以便将对象分配保持在最低限度(有时低至十几个周期)。”你能解释一下你在这里指的是哪个对象分配吗? @Geek 我的意思是分配对象所花费的执行时间保持在最低限度(我已经澄清了文本)。典型 Java 实现中的每个对象(包括数组)都会以一些字节开始,这些字节指示运行时类型、内在锁定监视器、可能与 GC 相关的位和身份哈希码。实际细节可能相当复杂,因为它需要进行大量优化。 @Lil 对象上的标识和监视器很少使用,但它们仍然始终存在。这严重阻碍了 JVM,但是你去。您建议将标题扩展到哪里。停止机器并跟踪每个使用过的对象的每个传入引用? / 你是对的,通常会使用少于四个字节的几位作为哈希码。一些实现可能会有特殊的事情,例如在同步期间将哈希复制到堆栈中,以便为良好的竞争锁定行为腾出更多空间。不需要反对票,IMO。【参考方案2】:

在回答第二个问题时,无论实现如何,多个对象都可能具有相同的identityHashCode。

请参阅bug 6321873,了解有关 javadoc 中措辞的简要讨论,以及演示非唯一性的程序。

【讨论】:

是的。两个不同的对象可以有相同的 hashCode。所有散列函数都是这种情况(在大于其结果大小的域上)。 @Thilo:JVM 可以以这样的方式编写,以保证,如果同时存在的对象不超过 40 亿,identityHashCode 将永远不会返回一个与仍然存在的任何其他对象一起返回。根据内存管理器的实现方式,这可能会很昂贵,或者可能会增加零额外成本。例如,Object 可以包含指向指针表的索引,只要每个对象存在,就会为每个对象分配一个不可变的表槽。典型的 JVM 实现不会那样做... ...但是其他一些“基于句柄”的内存管理方案确实如此,因此可能值得记录一下 JVM 在第一次询问对象身份时本质上选择了一个任意数字哈希码,然后将其存储以供以后使用[顺便说一句,我不记得曾经阅读过任何正式记录identityHashcode 是否是线程安全的东西。如果从未检索到对象的哈希码,是否可以保证同时“首次”调用该对象上的 identityHashCode 会产生相同的值?【参考方案3】:

HotSpot 中对象的头部由一个类指针和一个“标记”字组成。

标记字的数据结构源代码可以在markOop.hpp文件中找到。在这个文件中有一条注释描述了标记词的内存布局:

hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)

这里我们可以看到,32位系统上普通Java对象的身份哈希码保存在标记字中,长度为25位。

【讨论】:

【参考方案4】:

实现散列函数的一般准则是:

同一对象应返回一致的 hashCode,它不应随时间变化或依赖于任何可变信息(例如,由随机数或可变成员字段值播种的算法 哈希函数应该有一个良好的随机分布,我的意思是如果你把哈希码看作桶,2个对象应该尽可能映射到不同的桶(哈希码)。 2 个对象具有相同哈希码的可能性应该很少 - 尽管它可能发生。

【讨论】:

【参考方案5】:

据我所知,这是为了返回引用而实现的,它在对象的生命周期内永远不会改变。

【讨论】:

所以你说引用不是真正的内存地址(或直接派生自那个)。那么它是一种指向真实内存地址的指针吗?

以上是关于JVM 如何确保 System.identityHashCode() 永远不会改变?的主要内容,如果未能解决你的问题,请参考以下文章

JVM(Java 虚拟机) 监控工具

你如何设置 bashScriptExtraDefines?

不一样的视角:从JVM内存模型谈线程安全

不一样的视角:从JVM内存模型谈线程安全

如何搭建沙盒环境

秋招总结JUC和JVM(秋招总结)