hashCode() vs System.identityHashCode() are memory address abstraction in Java
public class ReferenceDemo {
public static void main(String[] args) {
String myString = "Hello";
// Print the identity hash code (based on the memory address) before the call
System.out.println("Before method call:");
System.out.println("myString Hash Code " + myString.hashCode());
System.out.println("myString reference value: " + System.identityHashCode(myString));
testPassByValue(myString);
// Print after the call to prove original reference is unchanged
System.out.println("After method call:");
System.out.println("myString Hash Code " + myString.hashCode());
System.out.println("myString reference value: " + System.identityHashCode(myString));
}
public static void testPassByValue(String str) {
System.out.println("\nInside method (at start):");
System.out.println("str hash code value: " + str.hashCode());
System.out.println("str reference value: " + System.identityHashCode(str));
// Reassigning the local parameter 'str' to a completely new string object
//str = "World";
//str = "Hello"; // str reference value: 940060004, its same for all System.identityHashCode in this code
// str = new String("Hello"); // str reference value: 234698513
System.out.println("\nInside method (after reassignment):");
System.out.println("str hash code value: " + str.hashCode());
System.out.println("str reference value: " + System.identityHashCode(str));
}
}A common point of confusion is whether hash codes represent memory addresses. The behavior splits between content-based and identity-based calculation.
| Feature | Object.hashCode() (Default) | Overridden hashCode() (e.g., String, Integer) | System.identityHashCode(Object x) |
|---|---|---|---|
| Basis | Object Identity / Memory-derived | Object Content (fields/chars) | Object Identity / Memory-derived |
| Content Dependency | No | Yes | No |
| Can it be overridden? | N/A (base implementation) | Yes (custom algorithms) | No (enforced by JVM) |
The
StringExceptionThe
Stringclass overrideshashCode()to calculate a hash based exclusively on its characters. Two different string objects with the text"Hello"will produce the same.hashCode(), even though they live in different memory locations.
String Interning
In our experiment:
String myString = "Hello";
testPassByValue(myString);Inside the method:
str = "Hello"; // Same identityHashCode across calls!
str = new String("Hello"); // Different identityHashCode!Why did str = "Hello" return the exact same identity hash?
- String Literal Pooling (Interning): The JVM maintains a special memory region called the String Constant Pool.
- When you declare a string literal like
"Hello", the JVM checks if it already exists in the pool. SincemyStringalready placed it there,str = "Hello"points to that exact same shared instance in memory. - If you force a new allocation using
str = new String("Hello"), you bypass the pool and force the JVM to allocate a separate object on the heap, producing a distinct identity hash.
Object Headers and Default Hash Codes
Does Object.hashCode() or System.identityHashCode() map properly to a physical memory address?
Not directly to a raw C-style physical memory address.
In modern JVMs (like HotSpot), an object’s identity hash code is generated and stored inside the Object Header (the Mark Word) when the hash code is first requested. Depending on JVM configurations and flags (such as -XX:HashCode), the JVM might generate it using:
- A random number generator.
- A memory-address-derived value.
- An internal sequence counter.
Because the Garbage Collector (GC) frequently moves objects around in memory during compaction to prevent fragmentation, a fixed physical address would change.
- If the identity hash code were a direct physical memory address, moving an object would change its hash code.
- This would instantly break any collection relying on hash values, such as a
HashMaporHashSet(the object would effectively “disappear” inside the map because its bucket lookup would change).
Therefore, the identity hash code is a stable proxy for identity, but it is not a raw pointer to a physical memory location.
Can different objects have the same default hash code?
Yes. Because hashCode() returns a 32-bit signed integer (int), there are a finite number of possible values (
Object Header Structure (HotSpot JVM)
Every object on the heap starts with a hidden overhead header before its actual fields:
- Mark Word (64 bits): Stores runtime metadata, including:
- Locking state (for synchronized blocks/monitors).
- Garbage collection aging bits.
- The Identity Hash Code (generated lazily and cached here once computed).
- Klass Word: A pointer pointing to the class metadata definition in the Method Area.
Where are the implementations of these two methods? What is @IntrinsicCandidate?
Both Object.hashCode() (when un-overridden) and System.identityHashCode(Object x) rely on native implementations inside the JVM’s C++ codebase (HotSpot runtime).
@IntrinsicCandidate
public static native int identityHashCode(Object x);- As seen in the Javadoc snippet,
identityHashCodeis marked as@IntrinsicCandidateand declarednative.native:- Means the method is implemented in C/C++ within the underlying JVM runtime (HotSpot) rather than Java bytecode.
@IntrinsicCandidate:- Tells the Just-In-Time (JIT) compiler: “This method is performance-critical. Instead of making a standard slow JNI (Java Native Interface) call, feel free to replace it entirely with optimized, hand-crafted machine assembly instructions generated directly by the compiler.”
This means the Java bytecode compiler and Just-In-Time (JIT) compiler bypass standard Java execution and replace it directly with optimized machine code or a call into the C++ runtime (specifically inside files like synchronizer.cpp or objectMonitor.cpp) that reads or generates the hash stored in the object’s header Mark Word.