Hotspot approaches to implement non-contending locking
Introduction
In the hotspot JVM, fast locking is a type of locking that is optimized to add as little overhead as possible when a single thread tries to acquire the lock for an object.
Note that as soon as multiple threads compete to acquire the lock for an object, the lock is promoted to a full monitor. In Hotspot, it’s common to refer to this process as inflating a monitor. It’s also common to refer to the type of lock that is used to implement fast locking as thin locks and to full monitors as fat locks.
Thin locking also doesn’t cover await / notify / notifyAll, thin locks are inflated
when multiple threads rely on those methods to cooperate.
In this article we will go through the different approaches that hotspot has used to implement fast-locking.
Historical evolution of fast locking
Before thin locking, fast locking relied on a monitor cache that had to be locked before performing certain operations to avoid race conditions. On top of it, the monitor added significant space overhead when a large number of object monitors were created.
Thin locks - Displaced mark word
According to the paper Thin locks: featherweight synchronization for Java, thin locking was first implemented for IBM’s version of the JDK 1.1.2 for AIX. Thin locking that is described in the aforementioned paper, brought fast locking by relying on using a part of the object header to contain:
- A flag that indicated whether an object was locked / unlocked.
- A pointer to the stack of the thread that was holding the lock.
- A count to reflect the number of times that the same thread has acquired an object lock.
See a more detailed explanation in Hotspot JVM’s Fast Locking based on displaced mark word.
Biased locking (Deprecated)
Then biased locking was introduced to avoid having to reduce the need to always run a compare-and-swap (aka CAS) operation whenever the same thread was trying to acquire a lock that it was already holding. However biased locking introduced unnecessary complexity and JEP-374 was created to deprecate it.
Thin locks - Lock stack
Recently the implementation of fast-locking has been challenged by project Lilliput, a project focused on reducing the memory footprint of objects in the heap by reducing the size of object headers. The previous thin locking implementation had several drawbacks:
- It used a significant amount of bits in the object header for locking, while most objects are never used for locking purposes.
- It made it difficult to add changes to the object header layout, because the same bits in the object header could sometimes be unused, sometimes point to the stack of the thread holding the lock or in the case of a fat lock could hold a pointer to the object monitor.
An explanation of the reasoning behind the changes to locking introduced by project Lilliput, is also provided in this presentation: Project Lilliput - Compact Object Headers.
The approach brought by project Lilliput addressed the existing drawbacks by dropping the pointer to the stack from the object header and replacing it by a lock stack that is kept per JavaThread object and contains the locks that a java thread is holding. If a thread wants to know if it’s holding the lock on an object it only needs to iterate over the lock stack.