两个不同的 Class 实例给出相同的 hashCode

Two different Class instances giving same hashCode

我在 JBoss 服务器上遇到一个奇怪的问题,其中两个 class 生成相同的 hashCode()

Class<?> cl1 = Class.forName("fqn.Class1");
Class<?> cl2 = Class.forName("fqn.Class2");
out.println(cl1.getCanonicalName());
out.println(cl2.getCanonicalName());
out.println(cl1.hashCode());
out.println(cl2.hashCode());
out.println(System.identityHashCode(cl1));
out.println(System.identityHashCode(cl2));
out.println(cl1 == cl2);
out.println(cl1.equals(cl2));
out.println(cl1.getClassLoader().equals(cl2.getClassLoader()));

产生:

fnq.Class1
fnq.Class2
494722
494722
494722
494722
false
false
true

我通常不会在意,但我们正在使用一个框架,该框架使用由 class 的哈希码和 属性 名称组成的密钥来缓存 setter。这是一个糟糕的缓存设计,但目前我无法控制(最新的 OGNL 3.0.6 Struts 2.3.24,参见 source。更新的 OGNL 解决了这个问题,但它不会' Struts 直到 2.5,目前处于测试阶段。)

让我觉得这个问题有些奇怪的是

我读到,如果存在竞争线程,Hotspot 中的 RNG 哈希码生成器(“0”策略)会生成重复项,但我无法想象 class加载会触发该行为。

Hotspot 在创建 Class 实例时是否使用特殊的哈希码处理?

  1. java.lang.Class 不会覆盖 hashCode,JVM 也不会以某种方式特殊处理它。这只是从 java.lang.Object.
  2. 继承的常规身份 hashCode
  3. -XX:hashCode=0(在JDK 6 和JDK 7 中默认)身份哈希码是使用全局Park-Miller 随机数生成器计算的。该算法生成周期为 2^31-2 的唯一整数,因此除了以下原因外,两个对象几乎没有机会具有相同的 hashCode。
  4. 由于该算法依赖于一个非同步的全局变量,因此确实存在两个不同线程由于竞争条件而生成相同随机数的可能性(the source)。这显然是您的情况。
  5. Identity hashCode 不是在创建对象时生成的,而是在第一次调用 hashCode 方法时生成的。因此 类 何时以及如何加载并不重要。如果同时调用 hashCode,则任何两个对象都可能发生此问题。
  6. 我建议使用 -XX:hashCode=5(JDK 8 中的默认值)。此选项使用线程本地 Xorshift RNG。它不受竞争条件的限制,也比 Park-Miller 算法更快。