【问题标题】:Hashcode doesn't change between reruns哈希码在重新运行之间不会改变
【发布时间】:2015-07-19 13:22:27
【问题描述】:
object Main extends App {
  var a = new AnyRef()
  println(a hashCode)
}

我在 Intellij Idea 中有这段代码。我注意到哈希码在重新运行之间没有变化。更重要的是,如果我重新启动想法,或者对代码进行一些轻微的修改,它不会改变。我可以重命名变量a 或添加更多变量,我仍然拥有相同的哈希码。

它是否缓存在某个地方?还是只是操作系统为变量分配了相同的地址?这有什么后果吗?

我希望它每次都是新的,因为操作系统应该在每次运行时分配新地址。

【问题讨论】:

    标签: scala intellij-idea


    【解决方案1】:

    Object.hashCode() 的实现可以在 JVM 之间有所不同,只要它遵守约定即可,这不需要运行之间的数字不同。对于 HotSpot,甚至还有一个选项 (-XX:hashCode) 来更改实现。

    HotSpot 的默认设置是使用随机数生成器,因此如果您使用它(没有 -XX:hashCode 选项),那么它似乎在每次运行时使用相同的种子,从而产生相同的哈希码序列。这没有什么问题。

    lmm 的回答是不正确的,除非您使用带有 -XX:hashCode=4 的 HotSpot 或其他默认使用此技术的 JVM。但我对此完全不确定(您可以尝试使用带有 -XX:hashCode=4 的 HotSpot 并查看是否获得另一个在运行之间也保持不变的值)。

    查看不同选项的代码: http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/tip/src/share/vm/runtime/synchronizer.cpp#l555

    那里有一条关于将“else”分支设为默认分支的注释,即Xorshift 模式,它确实是一个伪随机数生成器,它将始终提供相同的序列。 this question 上“apangin”的回答说,这确实已成为自 JDK8 以来的默认设置,这解释了您在评论中描述的与 JDK7 的变化。

    我可以确认这是正确的,查看JDK8源码: http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/runtime/globals.hpp#l1127

    --> 现在默认值为5,对应“else”分支(Xorshift)。

    【讨论】:

    • 这比我预期的更有趣 :) 我很确定你适合 JVM7,但我怀疑 hashCode=0 是 JVM8 的默认实现。我试图找到任何提示,但现在没有运气。大概需要在源码中找到一个设置的地方。问题是我在运行 JVM7 时每次运行都会收到不同的数字,但对于 JVM8,我开始收到相同的数字。可能是 JVM8 开始使用相同的种子,而 JVM7 没有,但为什么呢?这似乎很难改变。至少我很确定 JVM8 与 JVM7 相比发生了一些变化
    • @Archeg 做了更多的研究,看来我的回答是正确的(随机生成器每次都会产生相同的序列)。我添加了更多信息。
    【解决方案2】:

    一些实验:

    scala> class A extends AnyRef
    defined class A
    
    scala> val a1= new A
    a1: A = A@5f6b1f19
    
    scala> val a2 = new A
    a2: A = A@d60aa4
    
    scala> a1.hashCode
    res19: Int = 1600855833
    
    scala> a2.hashCode
    res20: Int = 14027428
    
    scala> val a3 = new AnyRef
    a3: Object = java.lang.Object@16c3388e
    
    scala> a3.hashCode
    res21: Int = 381892750
    

    所以,很明显 AnyRef 哈希码等于对象的地址。如果我们的哈希值相等,则意味着每次重新运行时对象地址都是相同的。这对我来说是真的,有两个回复。

    API 讲述 AnyRef hashCode 方法:

    引用类型的 hashCode 方法。请参阅 scala.Any 中的 hashCode。

    关于任何方法:

    计算对象的哈希码值。 默认散列算法取决于平台。

    我猜是平台决定了对象的位置,因此决定了 hashCode 的值。

    【讨论】:

    • 这其实很奇怪。特别是你有两个 REPL 的相同哈希。这是否意味着这些 ref 指向内存中的同一个对象?或者每个 REPL 打开它自己的 JVM 实例,它们只是具有相同的偏移量?
    • 后一种选择。每个 repl 在自己的 VM 中运行。
    【解决方案3】:

    任何新进程都从操作系统获得自己的虚拟地址空间。因此,虽然每次程序运行时进程可能存在于不同的物理地址,但每次都会映射到相同的虚拟地址。 (ASLR 存在,但我知道 JVM 不参与其中)。你可以看到这个,例如一个带有字符串常量的小型 C 程序(您可能必须故意为该程序禁用 ASLR) - 如果您获取指向字符串常量的指针并将该指针打印为整数,则每次都将是相同的值。

    【讨论】:

    • 谁说他的 JVM 使用内存地址来生成哈希码?
    【解决方案4】:

    hashCode() 不是随机数。它是分析对象的某些部分的消化结果。具有相同值的对象很可能具有相同的哈希码。这适用于您的情况,因为没有字段的 AnyRef 的“值”基本上是空的。

    【讨论】:

    • 不确定我是否同意你的看法。如果我实例化两个AnyRef 类型的实例,它们将始终具有两个不同的哈希码。如果我在AnyRef的情况下理解正确,它应该从java中获取new Object().getHashCode()的哈希码,这是内存中对象的地址。这对于两个不同的实例总是不同的,并且可能应该在运行之间改变。其他类型你可能是对的,但是AnyRef 没有字段,所以它应该生成不同的哈希码
    • 我看你是对的。我的假设是错误的。但是,对于您提供的示例代码,对象将始终分配在堆中完全相同的位置,因为 JVM 是新的。因此, hashCode() 将是相同的。我假设(我又来了!)您有一个更大的示例来演示意外行为。
    • 这是 JVM 非常有趣的属性。我可以在任何地方阅读更多相关信息?
    • 可能是源代码。但它不是一个属性,因为它不是一个可以依赖的值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-16
    • 2021-09-05
    • 2019-10-28
    • 2010-12-28
    • 2014-12-31
    • 2012-07-07
    • 2016-03-10
    相关资源
    最近更新 更多