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


70

通常,默认实现Object.hashCode()是内存中对象分配地址的某些功能(尽管JLS并未强制执行此功能)。既然VM会在内存中绕开对象,为什么System.identityHashCode()在对象的生存期内,返回的值从不改变?

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


1
相关问题:该内存地址是真实的内存地址还是虚拟的,即使对象被拖曳也可以保持固定?如果是虚拟的,那会很好,因为不需要调整指向它的指针。另一方面,这将意味着额外的间接访问和潜在的大映射表。
Thilo

3
首次请求时,地址稍有重新排列。(将低位的哈希码全部归零并不是一件
好事

实际上,在哪里说identityHashCode绝不能更改?System.identityHashCode的JavaDoc尚不清楚。
Thilo

当然,如果identityHashCode确实发生了变化,则只能使用将hashCode()实现为哈希表中的键的对象。
Thilo 2009年

3
好的,知道了:“只要在Java应用程序执行期间在同一个对象上多次调用(hashCode),hashCode方法就必须始终返回相同的整数,只要不修改该对象的equals比较中使用的信息即可。 ” 在这种情况下等于对象身份比较。
Thilo

Answers:


41

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

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


5
正确-我刚刚在syncnizer.cpp(vm运行时源代码)中通过ObjectSynchronizer :: FastHashCode进行了查看,生成哈希码后,看起来好像将其合并到了对象标头中。看起来HashCode有几种可能的实现;您提到的对所有对象均返回1的那个用于确保VM的任何部分都不假定哈希码出于任何原因是唯一的。
butterchicken

公共静态本机int identityHashCode(Object x); 是本机方法。您是否可以从本机实现的代码角度进行解释?我的意思是C ++实现。它主要用于inIdentityHashMap中,对吗?
Clark Ba​​o

@Tom对象标头是什么意思?您还写道:“我认为通常只在首次使用时才计算该值,以使对象分配保持最小(有时低至十几个周期)。” 您能解释一下这里指的是哪个对象分配吗?
极客

1
@Geek我的意思是将分配对象所花费的执行时间保持在最低限度(我已经澄清了文字)。在典型的Java实现中,每个对象(包括数组)都将从一些字节开始,这些字节指示运行时类型,内在锁定的监视器,可能与GC相关的位以及标识哈希码。实际细节可能非常复杂,因为需要对其进行大量优化。
汤姆·霍汀-钉路

@Lil Identity和对象的监视器很少使用,但它们仍然始终存在。这严重地妨碍了JVM,但是您就可以了。您建议将标题扩展到哪里。停止机器并跟踪如此使用的每个对象的每个传入参考?/是的,因为通常会将短于四个字节的几位用于哈希码。一些实现可能有一些特殊的事情,例如在同步期间将哈希复制到堆栈上,以便为好的竞争锁行为留出更多空间。IMO无需投票。
Tom Hawtin-大头钉

16

回答第二个问题,与实现方式无关,多个对象可能具有相同的identityHashCode。

有关Javadoc中的措辞的简短讨论以及用于演示非唯一性的程序,请参见错误6321873


1
真正。两个不同的对象可以具有相同的hashCode。所有哈希函数(在大于其结果大小的域中)都是这种情况。
Thilo 2009年

1
@Thilo:JVM的编写方式可以保证,如果一次存在的对象不超过40亿个,则identityHashCode绝不会返回已经与其他任何存在的对象一起返回的值。根据内存管理器的实现方式,这可能会很昂贵,或者可能会增加零附加成本。例如,一个Object可能包含一个指向指针表的索引,每个对象只要存在就不变地分配了一个表槽。典型的JVM实现无法做到这一点……
supercat 2013年

...但是其他一些“基于句柄”的内存管理方案也可以,因此,有必要记录一下JVM实质上是在第一次要求对象提供其身份哈希代码并随后将其存储以供选择时随机选择一个任意数字后来使用[btw,我不记得曾经读过任何东西来正式记录是否identityHashcode线程安全。如果从未检索过对象的哈希码,是否可以保证identityHashCode在该对象上同时进行“首次”调用会产生相同的值?
超级猫

2

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

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

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

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


0

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

  • 同一对象应返回一致的hashCode,它不应随时间变化或依赖于任何变量信息(例如,由随机数或可变成员字段的值作为种子的算法)
  • hash函数应该具有良好的随机分布,这就是说,如果您将哈希码视为存储桶,则2个对象应尽可能映射到不同的存储桶(哈希码)。这2个对象将具有相同的哈希码的可能性,应该是罕见的-虽然它可能发生。

-4

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


因此,您说的是引用不是真实的内存地址(或直接从该地址派生)。那么,这是否是指向实际内存地址的指针?
Thilo 2009年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.