I'm having some trouble understanding what the lifecycle of lock records is when dealing with "thin-locks" on HotSpot.
My understanding is that:
When a thread
T
first attempts to acquire a lock on objecto
, it triggers a "thin lock" creation -- alock record
is created onT
's stack, on the current frameF
, and a copy of themark work
(that will now be referred as adisplaced header
) plus a reference too
is stored onF
. Through a CAS operationo
's header is made to reference the lock record (and the last two bits are set to00
to mark this object as thin-locked!).There are multiple reasons why the CAS operation could fail, though:
- Another thread was quicker to grab the lock, we'll need to turn this thin-lock into a full-blown monitor instead;
- The CAS failed but it can be seen that the reference to the lock record belongs to
T
s stack, so we must be attempting to re-enter the same lock, which is fine. In that case, the lock record of the current stack-frame is kept null.
Given this, I have a couple of questions:
- Why would we create a new lock record each time we attempt to enter a lock? Wouldn't it be preferable to just keep a single lock record for each object
o
? - When leaving a synchronized block, I failed to understand how can the VM know whether we should release the lock or whether we're still "unwinding" from a recursive lock.
Anyone could shed some light on this?
References
- https://blogs.oracle.com/dave/lets-say-youre-interested-in-using-hotspot-as-a-vehicle-for-synchronization-research
- https://wiki.openjdk.java.net/display/HotSpot/Synchronization
- http://www.diva-portal.org/smash/get/diva2:754541/FULLTEXT01.pdf
- https://www.oracle.com/technetwork/java/biasedlocking-oopsla2006-wp-149958.pdf
Let me quote a paragraph from the last link:
Whenever an object is lightweight locked by a monitorenter bytecode, a lock record is either implicitly or explicitly allocated on the stack of the thread performing the lock acquisition operation. The lock record holds the original value of the object’s mark word and also contains metadata necessary to identify which object is locked. During lock acquisition, the mark word is copied into the lock record (such a copy is called a displaced mark word), and an atomic compare-and-swap (CAS) operation is performed to attempt to make the object’s mark word point to the lock record. If the CAS succeeds, the current thread owns the lock. If it fails, because some other thread acquired the lock, a slow path is taken in which the lock is inflated, during which operation an OS mutex and condition variable are associated with the object. During the inflation process, the object’s mark word is updated with a CAS to point to a data structure containing pointers to the mutex and condition variable. During an unlock operation, an attempt is made to CAS the mark word, which should still point to the lock record, with the displaced mark word stored in the lock record. If the CAS succeeds, there was no contention for the monitor and lightweight locking remains in effect. If it fails, the lock was contended while it was held and a slow path is taken to properly release the lock and notify other threads waiting to acquire the lock. Recursive locking is handled in a straightforward fashion. If during lightweight lock acquisition it is determined that the current thread already owns the lock by virtue of the object’s mark word pointing into its stack, a zero is stored into the on-stack lock record rather than the current value of the object’s mark word. If zero is seen in a lock record during an unlock operation, the object is known to be recursively locked by the current thread and no update of the object’s mark word occurs. The number of such lock records implicitly records the monitor recursion count. This is a significant property to the best of our knowledge not attained by most other JVMs.
Thanks