为什么 Object.hashCode() return 在运行中具有相同的值

Why does Object.hashCode() return the same value across runs

HotSpot hashCode() 上的默认实现 returns a random value and stores it in the object header. This doesn't seem to have changed in Java 8 其中哈希值是通过调用 os::random():

计算的
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
  intptr_t value = 0 ;
  if (hashCode == 0) {
     // This form uses an unguarded global Park-Miller RNG,
     // so it's possible for two threads to race and generate the same RNG.
     // On MP system we'll have lots of RW access to a global, so the
     // mechanism induces lots of coherency traffic.
     value = os::random() ;
  } else
...

我想知道为什么 hashCode() 不断地 returns 相同的值,同样在关闭我通过执行下面的简单测试尝试的 JVM 之后,重新启动我的机器然后 运行 main()又一次。

public class SimpleTest {
  public static void main(String[] args) {
    Object obj = new Object();
    // This calls toString() which calls hashCode() which calls os::random()
    System.out.println(obj);
  }
}

如果 hashCode() 实际上是 os::random(),怎么每次输出都一样?


java -version 给出

java version "1.8.0_40"
Java(TM) SE Runtime Environment (build 1.8.0_40-b25)
Java HotSpot(TM) 64-Bit Server VM (build 25.40-b25, mixed mode)

注:

如果有人问自己什么 System.out.println(obj);,它调用 obj.toString() 如果对象是非 null 并产生类似 java.lang.Object@659e0bfd 的东西,与 hashCode()@之后的部分是对象的十六进制哈希码(与对象在内存中的位置无关,contrary to what the documentation suggests, which has led to misunderstandings)。

确定性行为使代码更易于调试,因为它可以复制。因此,实现往往会尽可能选择它。想象一下,如果哈希每次都不同,那么复制一些由于哈希冲突处理不当(例如,在哈希长度减少后)而失败的单元测试会有多困难。

要回答你的问题,我们首先要问第二个问题,"Why is os::random() seeded with a fixed seed?"

正如@DavidSchwartz 所建议的那样,拥有一个带有固定种子的 "random" 数字生成器非常有用,因为它可以为您提供任意但确定的行为。 JVM 开发人员可以调用 os::random() 并且仍然知道 JVM 的行为不依赖于任何外部因素。在其他好处中,这意味着 JVM 测试是可重复的;使用 "properly" 种子 RNG 将很难重现与 RNG 相关的故障。

现在我们可以回答原来的问题了,改写为"Why does HotSpot's implementation of Object.hashCode() use os::random()?"

这个问题的答案很可能只是因为它简单而且有效。哈希码需要均匀分布,这是 RNG 提供的。 JVM 这个领域中最简单、最容易访问的 RNG 是 os::random()。由于 Object.hashCode() 不保证这些值的来源,因此 os::random() 根本不是随机的并不重要。

您会注意到这只是一种可能的散列策略,还定义了其他几个(并由 hashCode global), including one which they will "likely make ... the default in future releases.

选择

最终,这只是一个实现细节。根本不需要更积极地随机化 Object.hashCode(),其他 JVM 完全有可能不这样做,或者其他操作系统的行为不同。事实上,在 Eclipse 中,当 运行 你的代码重复时,我看到了不同的哈希码。此外,Object.hashCode() 的约定表明典型的 JVM 实现根本不会以这种方式实现 Object.hashCode()

This is typically implemented by converting the internal address of the object into an integer


另请注意,您的测试仅验证 .hashCode() 的第一个 调用是否一致。在任何类型的多线程程序中,您都无法预料到这种行为。它还不依赖于 JVM 在执行期间调用 os::random() 中的任何其他内容,它可以在任何时候执行(例如,如果垃圾收集器依赖于 os::random() 之后 .hashCode() 调用的结果第一次 GC 将是不确定的。

在执行之间使用不同的值没有任何优势。事实上,它增加了难以重现错误的机会。

重要的是,在单次执行期间,两个对象被分配相同哈希码的概率很低。

如果有人发现一个种子产生 运行 的值,需要很长时间才能产生重复(或很长的 运行,重复很少),那么它是有意义的每次都从那个开始。

我还没有检查 Hotspot 的来源以确定是否有一些努力来挑选 'good' 种子。但关键是这里的目标是有一个良好的传播。不需要随机性,从执行到执行的变化充其量是无用的,最坏的情况是无益。