11

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?

Eugen Martynov
  • 19,888
  • 10
  • 61
  • 114
Glenn Lane
  • 3,892
  • 17
  • 31
  • 4
    I don't think they will change over time. That would be very weird. Can you verify that they are not already the same at the very beginning? – Thilo Sep 02 '15 at 03:31
  • @Thilo I can't be 100% that the hashcodes weren't always the same. All I know is that the caching mechanism I mentioned would certainly fail under this condition when attempting to set a framework action property. And historically, that's exactly where we've observed the issue. I've redeployed just now, and the hashcodes are different. I'll just have to wait a few days/weeks until the issue returns, and run the class test again. – Glenn Lane Sep 02 '15 at 04:03
  • Are those two classes subclasses of anything? They might have a parent that overrides `hashCode()`. – F. Stephen Q Sep 02 '15 at 04:13
  • 3
    @FSQ They are not instances of a particular Class. They are instances of `java.lang.Class`, which is `final` and generates the hashcode natively. – Glenn Lane Sep 02 '15 at 04:15
  • the identity hashcode is such a small number, which seems odd to me. – ZhongYu Sep 02 '15 at 04:51
  • Where do you run this code? – Aleksandr M Sep 02 '15 at 09:01
  • @AleksandrM Remote debugging – Glenn Lane Sep 02 '15 at 11:36
  • Maybe it is remote debugging issue? Can you try to run same code on server in some action/controller class? – Aleksandr M Sep 02 '15 at 11:40
  • @AleksandrM The OGNL method cache had an entry that also reflected the hashcode collision. The collision was also verified using remote debugging expression evaluation, and an MBean that permits arbitrary execution. – Glenn Lane Sep 02 '15 at 12:09

1 Answers1

7
  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.
apangin
  • 92,924
  • 10
  • 193
  • 247
  • 1
    Wow, an excellent explanation! I was *very* skeptical the classloader was initializing these classes in a multi-threaded manner... but your explanation makes that an inconsequential point. It *is* very possible these classes had their hashCode() called for the first time by concurrent threads, and I agree this is the only real explanation. Because of our framework constraints, your solution to change the hash code generation strategy is my best option. Bravo! – Glenn Lane Sep 03 '15 at 14:46