Summary
Reduce the size of object headers in the HotSpot JVM from between 96 and 128 bits down to 64 bits on 64-bit architectures. This will reduce heap size, improve deployment density, and increase data locality.
Goals
When enabled, this feature
- Must reduce the object header size to 64 bits (8 bytes) on the target 64-bit platforms (x64 and AArch64),
- Should reduce object sizes and memory footprint on realistic workloads,
- Should not introduce more than 5% throughput or latency overheads on the target 64-bit platforms, and only in infrequent cases, and
- Should not introduce measurable throughput or latency overheads on non-target 64-bit platforms.
When disabled, this feature
- Must retain the original object header layout and object sizes on all platforms, and
- Should not introduce measurable throughput or latency overheads on any platform.
This experimental feature will have a broad impact on real-world applications. The code might have inefficiencies, bugs, and unanticipated non-bug behaviors. This feature must therefore be disabled by default, and enabled only by explicit user request. We intend to enable it by default in later releases and eventually remove the code for legacy object headers altogether.
Non-Goals
It is not a goal to
- Reduce the object header size below 64 bits on 64-bit platforms,
- Reduce the object header size on non-target 64-bit platforms,
- Change the object header size on 32-bit platforms, since they are already 64 bits, or
- Change the encoding of object content (i.e., fields and array elements) or array metadata (i.e., array length).
Motivation
A Java object stored in the heap has metadata, which the HotSpot JVM stores in the object’s header. The size of the header is constant; it is independent of object type, array shape, and content. In the 64-bit HotSpot JVM object headers are between 96 bits (12 bytes) and 128 bits (16 bytes), depending on how the JVM is configured.
Objects in Java programs tend to be small. Experiments conducted as part of Project Lilliput show that many workloads have average object sizes of 256 to 512 bits (32 to 64 bytes). This implies that more than 20% of live data can be taken by object headers alone. Thus even a small improvement in object header size could bring a large improvement in footprint. Cutting down the header of each object from 128 to 64 bits means improving overall heap usage by more than 10%, since the header is a fixed cost for every object. A smaller average object size leads to improvement in memory usage, GC pressure, and data locality.
Early adopters of Project Lilliput who have tried it with real-world applications confirm that live data is typically reduced by 10%–20%.
Description
In the HotSpot JVM, object headers support many different features:
- Garbage collection — Storing forwarding pointers and tracking object ages;
- Type system — Identifying an object’s class, which is used for method invocation, reflection, type checks, etc.;
- Locking — Storing information about associated light-weight and heavy-weight locks; and
- Hash codes — Storing an object’s stable identity hash code, once computed.
The current object header layout is split into a mark word and a class word.
The mark word comes first, has the size of a machine address, and contains:
Mark Word (normal):
64 39 8 3 0
[.......................HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH.AAAA.TT]
(Unused) (Hash Code) (GC Age)(Tag)
In some situations the mark word is overwritten with a tagged pointer to a separate data structure:
Mark Word (overwritten):
64 2 0
[ppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppTT]
(Native Pointer) (Tag)
When this is done, the tag bits describe the type of pointer stored in the header. If necessary, the original mark word is preserved (displaced) in the data structure to which this pointer refers, and the fields of the original header (e.g., the age bits and hash code) are accessed by dereferencing the pointer to get to the displaced header.
The class word comes after the mark word. It takes one of two shapes, depending on whether compressed class pointers are enabled:
Class Word (uncompressed):
64 0
[cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc]
(Class Pointer)
Class Word (compressed):
32 0
[CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC]
(Compressed Class Pointer)
The class word is never overwritten, which means that an object’s type information is always available, so no additional steps are required to check a type or invoke a method. Most importantly, the parts of the runtime that need that type information do not need to cooperate with the locking, hashing, and GC subsystems that can change the mark word.
For compact object headers we remove the division between the mark and class words by reducing the size of the hash code and subsuming the class pointer into the mark word:
Header (compact):
64 32 7 3 0
[CCCCCCCCCCCCCCCCCCCCCCCCCHHHHHHHHHHHHHHHHHHHHHHHHHAAAASTT]
(Compressed Class Pointer) (Hash Code) (GC Age)^(Tag)
(Self Forwarded Tag)
Overwriting the mark word with a tagged pointer makes certain operations more complex since we lose direct access to the compressed class pointer, as discussed below.
Compact object headers are guarded by a new experimental runtime option, -XX:(+/-)UseCompactObjectHeaders
. This option is disabled by default.
Compressed class pointers
Today’s compressed class pointers encode a 64-bit pointer into 32 bits. JDK 8 introduced compressed class pointers, which first depended on compressed object pointers; JDK 15 remov