This is blog 7 in the OpenJ9 locking/synchronization series (blog 1). It covers an optimization 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.
In OpenJ9, lock reservation is an optimization of thin/flat locks, which are represented by the lock-words in the object-monitors. It exploits the thread locality of Java object locks. It reduces the need for atomic compare-and-swap (CAS) operations that stall the CPU.
Thin/flat locks (lock-words) are optimized for the following scenarios:
- Locking an unlocked object.
- Recursively locking an object.
- Locking an object that is only briefly contended.
During analysis of benchmarks, it was noticed that most objects are only ever locked by a single thread (> 60%), and most locking operations are “first repetitions” i.e. objects are repeatedly locked by the first thread that locks them (75-99%) . This is an example of thread locality.
Thread locality analysis suggests that we should optimize for an object being repeatedly locked by the same thread. If an object is reserved for a thread, then the thread can lock the object without using the CAS operation.
All objects are initially unreserved. Who decides to reserve objects for a thread? The JIT determines the objects to reserve using code analysis.
- Which objects are reservable? Classes with many small synchronized methods.
- Which methods should reserve on enter? Hot synchronized methods containing many locking operations.
A bit (RES) is introduced in an object-monitor’s lock-word (thin/flat lock) to state if an object is reserved.
The best case for locking an unlocked object becomes:
- Read the lock-word.
- If ((thread ID | RES) == lock-word) then increment the count.
No atomic compare-and-swap operation used.
The best case for unlocking an object becomes:
- Decrement the count.
Other threads cannot modify an object-monitor while it is reserved for a specific thread. So, the lock reservation needs to be cancelled in order to allow other threads to access an object-monitor. Cancelling a lock reservation is very expensive. The process of cancelling a lock reservation is shown in the figure below.
Ultimately, the efficiency of the lock reservation feature depends on the JIT’s ability to pin-point the reservable objects which won’t need the lock reservation to be cancelled.
OpenJ9 command line options for lock reservation:
- -XlockReservation (enable lock reservation)
- -Xjit:reservingLocks (enable reserving locks for hot methods on classes that can be reserved)
- -Xjit:reserveAllLocks (enable reserving locks for all classes and methods; debug only)
- Lock Reservation: Java Locks Can Mostly Do Without Atomic Operations by Kiyokuni Kawachiya, Akira Koseki, and Tamiya Onodera.
- Lock Reservation for Java Reconsidered by Tamiya Onodera, Kikyokuni Kawachiya, and Akira Koseki.
- TreeEvaluator::monentEvaluator: JIT evaluation during object-monitor enter for identifying reservable objects.
- TreeEvaluator::monexitEvaluator: JIT evaluation during object-monitor exit for identifying reservable objects.
- The above JIT code references are for the x86 architecture. JIT provides an implementation for each hardware architecture. Refer to an architecture specific J9TreeEvaluator.cpp to find other implementations of monentEvaluator and monexitEvaluator.
- cancelLockReservation: Code for cancelling a lock reservation.
- objectMonitorEnter and objectMonitorExit are impacted by the lock reservation feature.
If you missed the previous blog in this series, it covers the lock nursery (blog 6) feature, which reduces the memory usage by optimizing the memory allocation for the object-monitors’ lock-words.
[Note] Not all the command line options, mentioned above, may have customer support. Some options are only available for experimental work.