Die HotSpot JVM gewährleistet die Konsistenz von Object.hashCode() über die Objektverschiebung hinweg, indem sie den Wert einmal berechnet – typischerweise von der ursprünglichen Speicheradresse – und ihn im Mark-Wort des Objekt-Headers cacht, bevor ein Garbage-Collection-Zyklus das Objekt verschieben kann. Dieses Mark-Wort enthält ein spezielles Hash-Code-Feld zusammen mit einem Bit-Flag, das anzeigt, dass der Hash materialisiert wurde, was sicherstellt, dass nachfolgende Aufrufe den zwischengespeicherten Wert abrufen, anstatt ihn neu zu berechnen. Folglich bleibt der Identitäts-Hash stabil, selbst wenn Sammler wie G1 oder ZGC das Objekt an eine neue Adresse verschieben, da er von dem physischen Zeiger getrennt und in den unveränderlichen Header-Metadaten gespeichert wird.
Eine verteilte Webanwendung verwendete IdentityHashMap, um aktive Session-Objekte über mehrere Anwendungs-Knoten hinweg zu verfolgen, wobei sie System.identityHashCode() für die Cache-Affinitäts-Routing während Lastenausgleichsoperationen nutzte. Während des Spitzenverkehrs führte der ZGC-Low-Latency-Sammler häufige gleichzeitige Verschiebungen von Objekten der jungen Generation durch, um enge Pausenzeitziele einzuhalten. Wenn der Identitäts-Hash sich bei der Bewegung geändert hätte, würde die Sitzungsaffinität brechen, was dazu führte, dass Anfragen über Knoten verteilt werden und Konsistenzgarantien verletzt werden.
Ein Ansatz bestand darin, UUID-Instanzen für jede Session bei der Erstellung zu generieren und eine separate ConcurrentHashMap<UUID, Session> beizubehalten. Vorteile: Vollständige Unabhängigkeit vom Lebenszyklus des JVM-Objekts und von den Verschiebungsmechanismen. Nachteile: Fügt sechzehn Bytes Overhead pro Sitzungsobjekt hinzu und führt zu Allocationsdruck durch die UUID-Generierung, was möglicherweise die Zuteilungsrate während des Stoßverkehrs saturiert.
Das Team erwog, Sitzungsobjekte im Speicher mit JNI-Kritischen Referenzen zu fixieren, um eine GC-Verschiebung zu verhindern. Vorteile: Garantiert stabile Speicheradressen und damit stabile Identitäts-Hashes, die aus den Adressen abgeleitet sind. Nachteile: Fixiert gesamte Heap-Bereiche im ZGC, was Fragmentierung verursacht und die gleichzeitigen Verschiebe-Fähigkeiten des Sammlers untergräbt, was zu inakzeptablen Pausenzeiten führt.
Die gewählte Lösung nutzte die Garantie der JVM-Spezifikation, dass Identitäts-Hash-Codes konstant bleiben, kombiniert mit der Implementierung der Mark-Wort-Caching von HotSpot. Vorteile: Null zusätzlicher Speicher-Overhead, keine Allocationskosten und volle Kompatibilität mit aggressiven Sammlern wie ZGC. Nachteile: Erfordert Vertrauen in die Implementierungsdetails der JVM, obwohl im Dokument festgelegt.
Die Anwendung hielt perfekte Sitzungsaffinität während Millionen von ZGC-Zyklen aufrecht, ohne Fixierungen oder Hilfsbezeichner, und erreichte Pausenzeiten von unter einer Millisekunde, während die Integrität der IdentityHashMap gewahrt blieb.
Gibt System.identityHashCode() immer die aktuelle Speicheradresse des Objekts als Ganzzahl zurück?
Nein. Während die ursprüngliche Berechnung möglicherweise die Speicheradresse als Entropie nutzt, wird das Ergebnis sofort im Objekt-Header gespeichert und ändert sich danach nie wieder. Das bedeutet, dass die zurückgegebene Ganzzahl nicht den aktuellen Standort des Objekts nach der GC-Bewegung widerspiegelt, und Entwickler sollten es nicht als Zeiger oder Speicheradressenermittlung behandeln.
Kann der Identitäts-Hash-Code negativ sein, und wie gehen Sammlungen damit um?
Ja, jeder 32-Bit-Ganzzahlwert ist gültig, einschließlich negativer Zahlen. IdentityHashMap verarbeitet negative Hashs durch Maskierungsoperationen wie (h ^ (h >>> 16)) & (length-1), um Math.abs() zu vermeiden, das bei Integer.MIN_VALUE aufgrund von Überlauf im Zweierkomplement fehlschlägt.
Ist der Identitäts-Hash-Code garantiert einzigartig für alle Objekte?
Nein. Der 32-Bit-Ganzzahlraum ist kleiner als der potenzielle Heap-Adressenraum, sodass Kollisionen möglich sind. HotSpot verwendet ein Marsaglia's xor-shift-Schema oder adressenbasierte Hashung, die Werte gut verteilt, aber Einzigartigkeit ist nicht garantiert, was bedeutet, dass IdentityHashMap sich auf die Referenzeinheitlichkeit zur Unterscheidung stützt, und nicht nur auf Hash-Codes.