1. Major Java Virtual Machines (JVMs) in History
The JVM is the execution engine that runs Java bytecode. Over the decades, several major implementations emerged:
- Sun Classic VM (Java 1.0 - 1.2): The original JVM released by Sun Microsystems. It was a pure interpreter, lacked a Just-In-Time (JIT) compiler initially, and had very primitive memory management.
- Sun/Oracle HotSpot JVM (Java 1.3+ to Present): Originally developed by Longview Technologies (acquired by Sun in 1997) as an advanced Smalltalk VM and retargeted for Java. It introduced adaptive optimization and JIT compilation (Client C1 and Server C2 compilers). It became the default Sun JVM in Java 1.3 and remains the foundational engine behind OpenJDK today.
- IBM J9 (OpenJ9): Developed independently by IBM for enterprise environments, heavily optimized for high-density cloud workloads and deep diagnostic tracing.
- Azul Zing / Falcon JVM: Built specifically for ultra-low latency applications, utilizing specialized pause-less garbage collectors (C4).
- GraalVM: A modern, high-performance polyglot VM developed by Oracle that supports Ahead-Of-Time (AOT) compilation into native executables alongside traditional JIT execution.
2. History of Identity Hash Codes and Memory Pointers
The Early Days (Java 1.0 - 1.2)
- In early JVMs (like the Classic VM), computing an object’s default
hashCode()often literally involved casting the raw C-style heap memory pointer address of the object into a 32-bit integer. - The GC Conflict: This worked initially because early GCs were non-moving (mark-sweep without compaction), meaning objects never changed their physical address once allocated.
- Once copying and compacting garbage collectors were introduced to prevent memory fragmentation, raw pointer-based hashing immediately broke hash-based collections (
HashMap/HashSet) whenever an object moved. Runtimes had to transition away from raw addresses.
The Modern Solution: Lazy Caching in the Object Header
Modern HotSpot VMs completely abandoned address-derived hashing. Instead, when System.identityHashCode() is first invoked:
- The JVM generates a stable value (using multi-threaded pseudo-random number generators or sequence counters).
- It permanently writes and caches that value inside the object’s header.
- Subsequent calls retrieve the cached value directly, ensuring stability even when the Garbage Collector moves the object across the heap.
3. Evolution of the Java Object Header
Every object instance on the HotSpot heap carries a hidden metadata overhead header before its actual field data. Its internal architecture evolved significantly:
graph TD subgraph ObjectHeader [HotSpot Object Header Layout] MW[Mark Word: 32-bit or 64-bit metadata, locks, GC age, and cached Identity Hash Code] KW[Klass Word: Pointer to class metadata in Metaspace] AL[Array Length: Present only if the object is an array] end
Key Components of the Object Header:
-
The Mark Word: A multi-purpose bitfield that dynamically changes its state depending on what the object is currently experiencing:
- Locking/Synchronization States: Tracks lightweight locks, heavyweight monitors, and historically biased locking.
- GC Metadata: Stores generational object aging bits and forwarding pointers during garbage collection compaction.
- Identity Hash Code: Stores a 31-bit integer hash code once computed.
-
The Klass Word: A pointer referencing the internal C++
Klassstructure in the JVM’s Metaspace, defining the object’s class type, methods, and pointers to its static variables. -
Array Length (Arrays Only): A 4-byte integer slot included exclusively in array headers to enforce strict runtime bounds-checking.
Modern Innovations (Project Lilliput)
In recent Java versions, memory optimization initiatives like Project Lilliput aim to compress 64-bit object headers down to 32 bits, drastically reducing heap overhead and memory footprint for large-scale enterprise applications.
Reference
https://stackoverflow.com/questions/26357186/what-is-in-java-object-header