Два разных экземпляра класса, выдающих одинаковый хэш-код

Я встречаюсь с причудливой проблемой на сервере JBoss, где два класса создают один и тот же 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

Мне обычно все равно, но мы используем фреймворк, который кэширует сеттеры, используя ключ, состоящий из хэш-кодов из класса и имени свойства. Это плохой дизайн для кэширования, но сейчас он не поддается контролю (OGNL 3.0.6 в последней версии Struts 2.3.24, см. источник. Новый OGNL исправляет но он не будет в Struts до 2.5, в настоящее время в бета-версии.)

Что делает проблему несколько странной для меня, это

  • Проблема появляется после нескольких дней использования... и я уверен, что оба класса/свойства становятся кэшированными в течение этого времени. Это заставляет меня поверить, что hashcode экземпляра класса фактически меняется... они стали равными через несколько дней.
  • Мы наблюдали поведение в очень устаревшем Hotspot 1.6, а теперь на 1.7.0_80. Оба являются 32-битными строками на Sun Sparc
  • Отчеты JVM -XX: hashCode как "0"

Я прочитал, что генератор hashcode RNG в Hotspot (стратегия "0" ) может создавать дубликаты, если есть гоночные потоки, но я не могу себе представить, что загрузка классов вызывает это поведение.

Использует ли Hotspot специальную обработку hashcode при создании экземпляра Class?

Ответ 1

  • java.lang.Class не переопределяет hashCode, и JVM не обрабатывает его как-то специально. Это просто регулярный идентификатор hashCode, унаследованный от java.lang.Object.
  • Когда -XX:hashCode=0 (по умолчанию в JDK 6 и JDK 7), идентификатор hashCode вычисляется с использованием глобального генератора случайных чисел Park-Miller. Этот алгоритм создает уникальные целые числа с периодом 2^31-2, поэтому почти нет шансов, что два объекта имеют один и тот же хэш-код, за исключением причины ниже.
  • Поскольку этот алгоритм использует глобальную переменную, которая не синхронизирована, действительно существует вероятность того, что два разных потока генерируют одно и то же случайное число из-за состояния расы (источник). Это то, что, по-видимому, происходит в вашем случае.
  • Идентификатор hashCode не генерируется при создании объекта, а при первом вызове метода hashCode. Поэтому не имеет значения, когда и как загружаются классы. Проблема может возникнуть с любыми двумя объектами, если hashCode вызывается одновременно.
  • Я предлагаю использовать -XX:hashCode=5 (по умолчанию в JDK 8). В этой опции используется локальный Xorshift RNG. Он не подлежит условиям гонки и также быстрее, чем алгоритм Park-Miller.