Два разных экземпляра класса, выдающих одинаковый хэш-код
Я встречаюсь с причудливой проблемой на сервере 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.