This is blog 6 in the OpenJ9 locking/synchronization series (blog 1). It covers a memory optimization within OpenJ9 related to the object-monitors (blog 2), which are used to enforce synchronization for the Java objects. Before reading this blog, one needs to have knowledge about the object-monitors. Please read the blog on the object-monitors before reading this blog.
Lock Nursery Concepts
Generally, the object-monitor’s lock-word used in the lightweight lock for three-tier spinning is stored in an object’s header. This provides easy and efficient usage of the lock-word by the virtual machine (VM) and just-in-time (JIT) compiler. An outline of a traditional object structure with the lock-word is shown below:
In the above outline, memory is always allocated for a lock-word in an object’s header. This allocation can be inefficient if the lock-word is never used for object synchronization. Always allocating memory for the lock-word in an object can significantly increase the memory usage due to a large number of objects.
So, the lock-word should not always be stored in an object’s header for efficiently utilizing memory. In the outline of the object structure below, the object header no longer contains the lock-word.
How can a lock-word be stored if it is not in an object’s header? The concept of a lock nursery helps with storing lock-words that are not cached in an object’s header. The lock nursery implementation involves finding an object’s entry in the lock nursery via its hash code. The lock nursery employs techniques for improving the hash table performance, which are introduced in the paper below.
Space- and Time-Efficient Implementation of the Java Object Model by David F. Bacon, Stephen J. Fink and David Grove.
Lock-words are stored in an object’s header only for the objects that have a higher probability of using synchronization. The lock nursery is used for objects that have a lower likelihood of using synchronization. Using the lock nursery is less efficient than using the object header for storing the lock-word.
The following conditions are enforced in order to allow the JIT to continue optimization based on details about the types of objects:
- java.lang.Object always has a lock-word.
- If a j.l.Object that is an instance of class X has a lock-word, then all subclasses of class X have a lock-word in the same location, except for direct subclasses of j.l.Object. Otherwise, all j.l.Objects have a lock-word.
In the current implementation, lock-words are added as a hidden field. The lock-word field is added after the fields of the class in the hierarchy where the lock-word is introduced. This does not apply when the class is a subclass of j.l.Object. If a class is a subclass of j.l.Object, then the lock-word is inherited from j.l.Object. A few examples are shown below:
In order to allow us to quickly determine where the lock-word is stored in the object, the J9Class structure includes the field lockOffset. If lockOffset is -1 or 0, then the object does not have a lock-word; otherwise, lockOffset is the offset from the header where the lock-word resides.
So what happens if an object does not contain a lock-word in the object header? In this case, an alternate lock-word is available in the J9ObjectMonitor structure, which is named alternateLockword. Instances of the J9ObjectMonitor are used as the entries within the VM’s monitor table. This structure provides the heavyweight lock for an object when the lightweight locks do not succeed.
One problem with using the alternate lock-word is that getting the instance of the J9ObjectMonitor for an object requires a hash table lookup. Since the JIT cannot do this lookup, the fast compare-and-swap lock acquisition in the jitted code is prevented. In order to address this limitation, the existing J9ObjectMonitor cache is expanded to 32 entries for each thread. Jitted code queries for the J9ObjectMonitor in this cache use a subset of the object pointer bits as the key and use the alternate lock-word to do the fast compare-and-swap lock acquisition when it is available.
If the required J9ObjectMonitor is not present in the cache or acquisition fails, then we fall back to the slower path by calling out to the VM code because an Object can move during GC and the cache is cleared by the GC when appropriate.
By using the alternate lock-word, we continue to support three-tier spinning, even for the objects that do not have a lock-word in their object headers.
The lock-word is given to every object except Arrays and instances of classes such as:
We do not believe that the above classes will commonly be used for synchronization. Also, j.l.String and Array instances typically make up 50% of the objects in the heap.
Except for Arrays, the classes for which objects get lock-words can also be configured. Because of the structure of Arrays, the lock-word can only be placed in the object header, and this can only be changed at compile time.
Classes that do or do not get lock-words can be configured using the -Xlockword command line option. -Xlockword:what will show the current configuration. For example:
The first thing that can be adjusted is the lock-word mode by specifying -Xlockword:mode=X where X can be one of:
- default: Every object except for Arrays gets a lock-word.
- minimizeFootprint: Only the classes that contain synchronized methods or that are both inner classes and a direct subclass of j.l.Object get a lock-word.
- all: All classes except Arrays get a lock-word and all noLockword options are ignored. This option also allows the JIT to make more assumptions.
Once the mode is set then except for mode=all, lock-words can be added or removed from specific classes through the -Xlockword:lockword=… and -Xlockword:noLockword=…options. Options to -Xlockword can be strung together using commas, for example:
There can also be multiple -Xlockword entries on the command line with the mode being the superset of the options specified. Ordering is used to resolve conflicts, i.e. noLockword after lockword means that instances of the class do not get a lock-word.
In OpenJ9, the default lock-word settings are set in the default options files called options.default.
The lock nursery impacts the VM, JIT and garbage collector (GC) due to the variation in the object header and the location of the object-monitor.
All the code related to the lock nursery can be found by querying J9VM_THR_LOCK_NURSERY in the OpenJ9 codebase.
Important source code files:
- lockwordconfig.c: Contains the code for parsing the -Xlockword command line option.
- montable.c: Contains the code for managing the object-monitor hash table.
- monhelpers.c: Contains the code for object-monitor operations such as enter, exit, inflate, destroy, etc.
The next blog in the OpenJ9 locking/synchronization series covers OpenJ9’s lock reservation (blog 7) feature, which selectively removes the need for the atomic compare-and-swap operations in order to acquire an object-monitor.
If you missed the previous blog in this series, it covers OpenJ9’s adaptive spinning (blog 5) strategy, which dynamically enables/disables lock spinning using heuristics in order to avoid the negative impact of lock spinning.
[Note] Some of the above the command line options may not have customer support. Additionally, some options are only available for experimental work.