JDK-8198331 : [Lilliput] Remove mark word from objects
  • Type: JEP
  • Component: hotspot
  • Sub-Component: runtime
  • Priority: P4
  • Status: Draft
  • Resolution: Unresolved
  • Submitted: 2018-02-17
  • Updated: 2022-06-20
Related Reports
Relates :  
Description
Investigate downsizing average object size by removing (or downsizing) the mark word from the `oopDesc` layout.

DRAFT DRAFT DRAFT

The 64-bit mark word can be used for several purposes:

 - GC state during a GC, such as a forwarding pointer
 - lock state (not during a GC), including unlocked, thin-locked, biased, and inflated monitor
 - object annotations by GC (a few "age" bits)
 - storage for the `System.identityHashCode`
 - a tag to distinguish object instances from value instances

There are well-known alternative representations for all of these
mechanisms which do not use the 64-bit object header.

For example:

 - GC state can be stored in a side array
 - lock state can be stored in a side array and/or in a word preceding the object
 - age annotations and "value tags" can be inserted into other "waste" space of the object, such as the high bits of the klass field (if it is present)
 - hash codes can be stored in a word before the object, and/or be derived from the object's address in new-space

If we could remove mark words from all objects, the density of the heap would go up by a significant factor, basically `(1+8/S)`, where `S` is the average object size; since `S` is typically something like 30-50 bytes (including arrays, which may be quite large), the overhead of mark words can often be a double-digit percentage.

Comments
> (Side question: What do you mean by ZGC also uses them? Are you referring to the low 3 bits?) Yes, ZGC uses the lower 3 address bits of object references, AFAIK.
09-05-2022

Yes, I was referring to the oops, though not ZGC's coloured pointers specifically. Seems unfortunate that there isn't any free space left, that was the (only) easy approach I could think of (Side question: What do you mean by ZGC also uses them? Are you referring to the low 3 bits?)
09-05-2022

Do you mean colored pointers like they are used in ZGC? Yes, Java objects are aligned, and the lowest 3 bits are unused. Except when using compressed oops, then we don't have any bits left. And ZGC also uses them.
09-05-2022

Coming back to this discussion briefly, is there at least 1 unused bit in the current Lilliput object pointers? Right now it's the only truly "quick" way I can think of to check if a Mark Word exists on a particular object without any severe performance regressions or other issues (Which itself probably requires markWord to be be moved outside oopDesc, but that's a whole other can of worms)
08-05-2022

Sounds like a good idea to me to have this implemented for objects by default, where this assumption is made if a call to Object.hashCode is made (Not if the method is overridden in the subclass without calling super), though I'm unsure how checking at classfile loading would be different from checking it in javac (Maybe we could add a new argument for javac for this if it is useful for users?). The only concern I have is the rare case the analysis happens to be incorrect and the slow fallback path has to be used. It would probably be best if we combined this with https://github.com/openjdk/lilliput/pull/14 for the slowpath instead of a side table or a dedicated field. As for control over the resources required for hashing, I'm still of the opinion that an option (Through an annotation, method or keyword, whichever is best) for disabling hashing entirely on a certain object instead would be a little easier all round, since we can guarantee that it will never be required for System.identityHashCode. Combining this with the approach above for all other objects seems like a good choice, at least to me. That leaves us with just the GC portion of the Mark Word, which might unfortunately be too disruptive to implement for all the current GCs HotSpot already has
17-04-2022

I was thinking that we could maybe have javac resolve at compile time with absolute certainty whether a specific object requires hashing by seeing if identityHashCode is ever used on any particular instance (Since locking might end up not relying on the header eventually I'll ignore it; I was initially not aware of that), thus doing away with the need for checking whether it's actually required at runtime and having a fallback path ready (as with an estimation based approach that attempts to predict which objects do in fact need support), but I'm unsure if that would end up requiring metadata to be stored and checked at runtime that defeats the purpose of such an approach entirely. This might also affect Java code that loads other jars at runtime, which may then end up using said objects differently in a way that javac never accounted for Eliminating the GC part of the Mark Word would definitely require forcefully inlining the object however, which may be better suited for Valhalla than Lilliput. Unless there are plans to eliminate all current and future GCs depending on object headers too?
15-04-2022

javac cannot give the final word about whether an object requires an identity hash code, IHC. Anybody at any time for any reason can call `System.identityHashCode` on any object to get its IHC. However, the vast majority of such calls are via the inherited method `Object::hashCode`. We can certainly determine, either in javac or (better) when a classfile is loaded, that `Object::hashCode` is reachable. If so, assume the object needs "whatever resources" to memoize its IHC. If not, it might be profitable to speculate that the IHC is not going to be computed by any of the other paths. In that case, we still need a fallback, using something like a side table. We would hope, in that case, that the fallback is rarely used, since side tables are a pain, and probably use more footprint than if we had provisioned the "whatever resources" up front for that object. The "whatever resources" could be in the object header (as they are today), or they could even be a dedicated injected field in the object. In that case, perhaps some additional information would be useful to control the injection of that field, such as an annotation in the source code, either to opt in or opt out definitively to the extra footprint.
14-04-2022

How coincidental, I was discussing this with someone else earlier, but my posts to the list on the issue seem to all be rejected and not shown, I'm unsure if this is due to gatekeeping on the mailing list I'm unaware of Original post: Subject: Explicitly opting out of the Mark Word? Considering that, according to the Lilliput Wiki: "It is interesting to note that only relatively few (<1%) Java objects are ever used for locking ... It is interesting to note that with most workloads, only relatively few (<1%) Java objects are ever assigned an i-hash." What if we allowed marking a specific object during its declaration to explicitly opt out of some or all of these? (either identity hash, locking, or GC, or all 3 at these entirely). We could have something to signal at (javac) compile time that the object will not support any or all of these 3 operations on it, effectively meaning the entire Mark Word can be completely omitted from the header, which seems to me like a huge gain- We don't have to do so many VM level tweaks, the developer can choose themselves, giving them more flexibility, and best of all, eliminating half of the entire header somewhat free of cost. Realistically, we'd have to find a way to check for whether the Mark Word contains metadata for either of these 3 operations or not (or if it even exists for that particular object at all) without any runtime penalties (maybe we could put all of this information in the oops to avoid costly runtime header checks), and disabling GC for such objects would be much trickier. We could take a page from Valhalla (In which case I'll only talk about Identity Objects here, value objects and primitives may or may not be within the scope of this proposal, given that they can exist without headers at all) and forcefully inline all objects declared as "Without GC support or the entire Mark Word" to be stored in memory exactly where they are initialized, instead of separately on the heap. This would in effect allow them to take advantage of existing memory handling operations (Stack frames popping off once a scope returns, being destroyed when the object that it itself is stored in is destroyed by GC, etc). This would of course present its own set of challenges, such as setting the field of another object to an object created and destroyed within the scope that the setting occurs, but I'm confident we could find a way to work around that. Alternatively we could just make it all or none and only allow the full Mark Word or no Mark Word at all to make things simpler. Would this be worth looking into, or is this better suited for Valhalla? best regards, Julian Thinking back now maybe the @ForceInline annotation or the scrapped inline keyword from Valhalla could be used here somehow (If it's the latter, @ForceInline would probably become redundant)
14-04-2022

Yes. There may actually be a less explicit way of doing this sort of thing: we could determine whether or not an object is likely to not need any hashcode or locking based on its type. For example, arrays seem very likely to not be used as keys in hash structures or for synchronization. It would require a (slower) fallback-path for the unlikely case that the assumption is wrong (e.g. some sort of lookup-table on the side), but that seems doable. OTOH, we already have plans to reduce header-space for hashing to 2 bits (and allocate 32bit hashcode on-demand), see https://github.com/openjdk/lilliput/pull/14 and we are currently discussing if/how to avoid using lock bits altogether (by exposing obj->lock mapping externally, in a lookup-table or somehow else). Then we'd only need space for the Klass* (which we can currently compress to 22-24bits) and some extra for GC (e.g. object age) and other uses. This will get us to 32bit headers (incl Klass*) soon-ish. Everything beyond that would come with severely diminishing returns on much higher investments, but should certainly be considered.
14-04-2022

Subsumed by Project Lilliput.
13-04-2022