2015-03-26 21 views
8

Domyślna implementacja hashCode() na HotSpot zwraca wartość random value i zapisuje ją w nagłówku obiektu. To wydaje się nie zmieniły w Java 8 gdzie wartość hash oblicza się poprzez wywołanie os::random():Dlaczego funkcja Object.hashCode() zwraca tę samą wartość w różnych przebiegach

static inline intptr_t get_next_hash(Thread * Self, oop obj) { 
    intptr_t value = 0 ; 
    if (hashCode == 0) { 
    // This form uses an unguarded global Park-Miller RNG, 
    // so it's possible for two threads to race and generate the same RNG. 
    // On MP system we'll have lots of RW access to a global, so the 
    // mechanism induces lots of coherency traffic. 
    value = os::random() ; 
    } else 
... 

Zastanawiam się dlaczego hashCode() stale zwraca taką samą wartość, również po wyłączeniu JVM które próbowałem poprzez wykonanie prostego testu poniżej, ponowne uruchomienie komputera i ponowne uruchomienie main().

public class SimpleTest { 
    public static void main(String[] args) { 
    Object obj = new Object(); 
    // This calls toString() which calls hashCode() which calls os::random() 
    System.out.println(obj); 
    } 
} 

Jak wyjście być taka sama za każdym razem, gdy hashCode() jest rzeczywiście os::random()?


java -version daje

java version "1.8.0_40" 
Java(TM) SE Runtime Environment (build 1.8.0_40-b25) 
Java HotSpot(TM) 64-Bit Server VM (build 25.40-b25, mixed mode) 

Uwaga:

Gdyby ktoś zapytać się co System.out.println(obj);, który nazywa obj.toString() jeśli obiekt jest niezerowe i produkuje coś [email protected], musi z hashCode(): Część po @ jest kodem mieszania obiektu w szesnastkowy (i nie ma związku z lokalizacją obiektu w pamięci, contrary do tego, co sugeruje dokumentacja, co doprowadziło do misunderstandings).

+1

'os :: random()' będzie deterministyczne. Zawsze musi zaczynać się od tego samego początkowego materiału siewnego. – EJP

+0

Ah, założyłem, że seed jest bieżącym czasem w milisekundach (tak jak w przypadku 'Random' Java) –

+0

Zarodkiem metody jest początkowo' 1'. Zakładając, że nie zostanie zmieniony, pierwszą wartością będzie zawsze "mod (16807 * 1) (2 ** 31-1)". Następnie następne nasienie opiera się na tym i tak dalej. Nie znam wystarczającej liczby wewnętrznych elementów JVM, aby określić, czy to wszystko z tego powodu, czy też jest coś więcej. – Obicere

Odpowiedz

5

Aby odpowiedzieć na to pytanie, musimy najpierw zadać pytanie wtórnego, „Dlaczego os::random() zaszczepiono stałym seed?”

Jak sugerował @DavidSchwartz, posiadanie "losowego" generatora liczb ze stałym nasieniem jest bardzo użyteczne, ponieważ daje arbitralne, ale deterministyczne zachowanie. Programiści JVM mogą zadzwonić pod numer os::random() i nadal wiedzą, że zachowanie JVM nie zależy od czynników zewnętrznych. Wśród innych korzyści oznacza to, że testy JVM są powtarzalne; użycie "prawidłowo" zaszczepionego RNG mogłoby utrudnić odtworzenie awarii związanych z RNG.

Teraz możemy odpowiedzieć na oryginalne pytanie, przeformułowane jako "Dlaczego wdrożenie HotSpot Object.hashCode() używa os::random()?"

Odpowiedź na to pytanie jest prosta, ponieważ jest prosta i działa. Hasła hasłowe muszą być dobrze rozłożone, coś, co zapewnia RNG. Najprostszym, najbardziej dostępnym RNG w tym obszarze JVM jest os::random(). Ponieważ Object.hashCode() nie daje żadnych gwarancji co do źródła tych wartości, nie ma znaczenia, że ​​os::random() nie jest w ogóle losowy.

Zauważysz, że jest to tylko jeden z możliwych strategii mieszania, kilka innych są zdefiniowane (i wybrany przez hashCode globalny), w tym jedno, które będą „likely make ... the default in future releases.

Ostatecznie, jest to tylko szczegół realizacja . Po prostu nie ma potrzeby bardziej agresywnej randomizacji Object.hashCode() i jest całkiem możliwe, że inne maszyny JVM nie robią tego w ten sposób, lub inne systemy operacyjne zachowują się inaczej. W rzeczywistości w Eclipse widzę różne kody skrótu podczas wielokrotnego uruchamiania kodu. Ponadto, umowa o Object.hashCode() sugeruje, że typowe implementacje JVM nie wdrażają Object.hashCode() ten sposób na wszystkich:

Jest to zazwyczaj realizowane poprzez przekształcenie wewnętrzny adres obiektu do liczby całkowitej


Należy również zauważyć, że Twój test sprawdza tylko, czy pierwsze połączenie z jest zgodne. W jakimkolwiek programie wielowątkowym nie można oczekiwać takiego zachowania. Nie polega również na niczym innym w JVM wywoływania os::random() podczas wykonywania, co może zrobić w dowolnym momencie (na przykład, jeśli śmieciarz opiera się na os::random() wyniku wywołań .hashCode() po tym, jak pierwsza GC będzie niedeterministyczna).

6

Zachowanie deterministyczne ułatwia kodowanie debugowania, ponieważ można go replikować. Tak więc implementacje zwykle wybierają to, o ile to możliwe. Wyobraź sobie, jak trudno byłoby replikować niektóre testy jednostkowe, które nie powiodły się z powodu niewłaściwego zderzenia mieszania (powiedzmy, po skrócie skrótu), jeśli skróty były różne za każdym razem.

+2

To nie wydaje się wielkim powodem; jeśli piszesz testy zależne od zachowania '.hashCode()', powinieneś testować obiekty, które jawnie zdefiniowały metodę '.hashCode()'. Opieranie się na 'Object.hashCode()', aby zachować stabilność, powoduje tylko problemy, szczególnie w przypadku aktualizacji wersji JVM. Jeśli cokolwiek, jest to argument dla jawnego wywoływania 'Object.hashCode()' pomiędzy kolejnymi uruchomieniami. – dimo414

+0

Gdzie powiedziałem, że ktoś może lub powinien na nim polegać, pozostając stabilnym? Jeśli sprawisz, że będzie on niestabilny, możesz mieć test, który się nie powiedzie, ale nie możesz zreplikować tej awarii, co znacznie utrudni debugowanie. –

+0

Oczywiście, ale sugerujesz, że powodem stabilności jest pomoc w testowaniu.Po prostu nie uważam tego za bardzo dobry powód takiego zachowania; ludzie, którzy wiedzą lepiej, nie skorzystają z tego, a ludzie, którzy tego nie zrobią, będą zdezorientowani, dlaczego ich testy minęły, ale ich kod pękł później. – dimo414

1

Nie ma żadnej korzyści z posiadania różnych wartości między wykonaniami. W rzeczywistości zwiększa szansę na trudne do odtworzenia błędy.

Ważne jest to, że prawdopodobieństwo, że dwa obiekty mają ten sam kod hash, jest niskie podczas jednego wykonania.

Jeśli ktoś znalazł materiał siewny, którego rezultatem jest seria wartości, które trwają długo, aby wytworzyć powtórzenie (lub długą serię z bardzo małą liczbą powtórzeń), wtedy warto zacząć od tego za każdym razem.

Nie sprawdziłem źródła Hotspot, aby stwierdzić, czy podjęto pewne starania, aby wybrać "dobre" ziarno. Chodzi o to, że celem jest dobre rozproszenie. Losowość nie jest wymagana, a wariancja od wykonania do wykonania jest co najwyżej bezużyteczna, w najlepszym razie niepomocna.