一个32位的hashCode在Java中是如何存储在一个25位的mark word中而不丢失数据的?

问题描述 投票:0回答:1

我一直在研究 Java 对象的内部结构,对 hashCode 值的管理方式感到困惑。据我了解,Java 中的 hashCode 方法返回一个 32 位整数。但是这个hashCode是保存在对象的header中的,具体是25位的mark word中。

这给我提出了几个问题:

如何将一个 32 位的 hashCode 存储在一个 25 位的标记字中而不丢失一些数据位? 即使因为这个位长差异导致数据丢失,为什么我再次调用

hashCode()
时,它仍然检索到原始的hashCode值而没有任何明显的数据丢失?

任何关于 Java 如何做到这一点的见解都将不胜感激。

java jvm hashcode
1个回答
7
投票

首先也是最重要的:所有这些都是非常重要的实现细节,并且没有由规范定义。我专门讨论了最近的 OpenJDK 构建(我正在测试 JDK 17,但这种行为似乎存在了一段时间),但没有说其他 JDK 甚至未来版本的 OpenJDK 可以改变这一切。

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

  • identity hash code 是由 JVM 决定的一个值,它在对象的生命周期内保持不变,并且不受 Java 代码的影响(即覆盖

    hashCode()
    对此没有影响)。这个值可以通过调用
    System.identityHashCode(obj)
    获得。

  • 另一方面,hash code 是 Java 程序员最常与之交互的东西:调用对象时

    hashCode()
    的返回值。当任何东西存储在
    HashMap
    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);
    }
}

使用 64 位 JVM 运行时会打印

Smallest number of leading zeroes in identity hash codes of 1000000 objects = 1

而在 32 位 JVM 上打印

Smallest number of leading zeroes in identity hash codes of 1000000 objects = 7

当然,这不是绝对证据,但在测试一百万个对象时,这些值极不可能是巧合。

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

© www.soinside.com 2019 - 2024. All rights reserved.