Search code examples
javaognl

Two different Class instances giving same hashCode


I'm encountering a bizarre issue on a JBoss server where two classes are producing the same 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()));

Produces:

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

I normally wouldn't care, but we're using a framework that caches setters using a key that is comprised of hashcodes from the class and a property name. It's a bad design for caching, but it's beyond my control at the moment (OGNL 3.0.6 in the latest Struts 2.3.24, see source. A newer OGNL fixes the issue, but it won't be in Struts until 2.5, currently in beta.)

What makes the issue somewhat bizarre to me is

  • Problem appears after several days of usage... and I'm pretty sure both class/properties are getting cached during that time. This leads me to believe that the class instance hashcode is actually changing... they became equal after several days.
  • We've observed the behavior in a very outdated Hotspot 1.6, and now on 1.7.0_80. Both are 32-bit builds on Sun Sparc
  • JVM reports -XX:hashCode as "0"

I read that the RNG hashcode generator in Hotspot (the "0" strategy) can produce duplicates if there's racing threads, but I can't imagine classloading triggering that behavior.

Does Hotspot use special hashcode handling when creating a Class instance?


Solution

    1. java.lang.Class does not override hashCode, nor JVM handles it somehow specially. This is just the regular identity hashCode inherited from java.lang.Object.
    2. When -XX:hashCode=0 (default in JDK 6 and JDK 7) the identity hashCode is calculated using global Park-Miller random number generator. This algorithm produces unique integers with the period of 2^31-2, so there is almost no chance that two objects have the same hashCode except for the reason below.
    3. Since this algorithm relies on a global variable that is not synchronized, there is indeed a possibility that two different threads generate the same random number due to race condition (the source). This is what apparently happens in your case.
    4. Identity hashCode is not generated on object creation, but on the first call to hashCode method. So it does not matter when and how the classes are loaded. The problem can happen with any two objects if hashCode is called concurrently.
    5. I suggest using -XX:hashCode=5 (default in JDK 8). This option uses thread-local Xorshift RNG. It is not subject to race conditions and is also faster than Park-Miller algorithm.