Java中如何将32位hashCode存储在25位标记字中而不丢失数据?

Sad*_*Man 2 java jvm hashcode

我一直在研究 Java 对象的内部结构,并对如何管理 hashCode 值感到困惑。据我了解,Java中的hashCode方法返回一个32位整数。然而,这个 hashCode 存储在对象的标头中,特别是 25 位标记字中。

这向我提出了几个问题:

如何在不丢失某些数据位的情况下将 32 位 hashCode 存储在 25 位标记字中?即使由于这种位长差异而导致数据丢失,为什么当我hashCode()再次调用时,它仍然检索到原始的 hashCode 值,而没有任何明显的数据丢失?

如果您能深入了解 Java 如何做到这一点,我们将不胜感激。

Joa*_*uer 6

首先也是最重要的是:所有这些在很大程度上都是实现细节,而不是由规范定义的。我专门讨论了最近的 OpenJDK 版本(我正在测试 JDK 17,但这种行为似乎存在了一段时间),但没有任何迹象表明其他 JDK 甚至 OpenJDK 的未来版本可以改变这一切。

接下来,区分对象的身份哈希码和它的哈希码很重要。

  • 身份哈希码是由 JVM 决定的值,该值在对象的生命周期内保持不变,并且不会受到 Java 代码的影响(即覆盖hashCode()对此没有影响)。该值可以通过调用System.identityHashCode(obj)获取。

  • 另一方面,哈希码是 Java 程序员最常与之交互的内容:在对象上调用时的返回hashCode()。虽然每当任何内容存储在HashMapor HashSet(或类似结构)中时这都是一个重要的值,但 JVM 本身并不特别关心它。即使确实如此,它也无法将其存储在对象标头中,因为hashCode()每次调用它时都可能返回不同的值。

这两个定义以一种重要的方式相互作用:hashCode()的方法java.lang.Object(以及该方法未被重写的任何其他对象的方法)将返回身份哈希代码。因此,如果没有定义其他内容,可以说身份哈希码是哈希码的默认值。

查看相关代码后确实看起来在32位平台上最多有25位空间来存储身份哈希码。

但是hashCode被定义为32位宽,那怎么可能呢?

很简单:这些平台上的身份哈希码永远不会使用超过 25 个位,因此所有未存储的位都已知/假定为零。

虽然我没有找到决定这一点的具体位置(我也没有仔细观察),但可以使用如下代码轻松验证这一点:

public class MyClass {
    public static void main(String args[]) {
      int minLeadingZeroes = 32;
      for (int i = 0; i < 1_000_000; i++) {
          int hash = System.identityHashCode(new Object());
          minLeadingZeroes = Math.min(minLeadingZeroes, Integer.numberOfLeadingZeros(hash));
      }

      System.out.println("Smallest number of leading zeroes in identity hash codes of 1000000 objects = " + minLeadingZeroes);
    }
}
Run Code Online (Sandbox Code Playgroud)

当使用 64 位 JVM 运行时,会打印

public class MyClass {
    public static void main(String args[]) {
      int minLeadingZeroes = 32;
      for (int i = 0; i < 1_000_000; i++) {
          int hash = System.identityHashCode(new Object());
          minLeadingZeroes = Math.min(minLeadingZeroes, Integer.numberOfLeadingZeros(hash));
      }

      System.out.println("Smallest number of leading zeroes in identity hash codes of 1000000 objects = " + minLeadingZeroes);
    }
}
Run Code Online (Sandbox Code Playgroud)

而在 32 位 JVM 上它会打印

Smallest number of leading zeroes in identity hash codes of 1000000 objects = 1
Run Code Online (Sandbox Code Playgroud)

诚然,这不是绝对的证据,但在测试一百万个物体时,这些值不太可能是巧合。

另请注意,即使在 64 位 OpenJDK 构建中,身份哈希代码也最多使用 31 位(如上面链接的实现的注释中所述),尽管有大量空闲空间(在这种情况下许多位未使用)。

  • 还计划将 64 位 JVM 上的身份哈希码大小减少到 25 位,请参阅 [JEP 450:紧凑对象标头(实验)](https://openjdk.org/jeps/450) (4认同)