What Really Happens Inside Java’s synchronized? A Deep Dive into JVM Lock Mechanisms
This article examines the inner workings of Java’s synchronized keyword by analyzing HotSpot 1.8 source code, revealing how lightweight, heavyweight, biased, and adaptive spin locks operate, how lock inflation occurs, and how the JVM manages monitor objects, CAS operations, and thread queues to implement synchronization.
The author investigated the synchronized keyword by reading the HotSpot 1.8 source code, discovering that when a lightweight lock’s CAS fails the JVM does not spin; it inflates directly to a heavyweight lock. Spin only appears after the lock has been upgraded.
From Heavyweight Lock
Before Java 1.6 synchronized was implemented solely as a heavyweight lock, using the operating system’s mutex (e.g., pthread_mutex) which incurs a costly user‑kernel context switch.
Let’s look at the implementation of a heavyweight lock.
When a method or block is marked with synchronized, the compiler emits the bytecode instructions monitorenter and monitorexit. These instructions operate on a monitor object that is associated with the target object (the lock object). Each Java object has an internal monitor reference; acquiring the lock means obtaining ownership of that monitor.
The generated bytecode for a simple method shows a monitorenter before the critical section and a monitorexit after it, with the latter also handling exceptional exits to avoid deadlocks.
When a method is synchronized, the method’s access flags include ACC_SYNCHRONIZED (0x0021), which the JVM recognises at runtime to perform the same lock‑acquisition logic.
Monitor Internals
The monitor implementation in HotSpot is the C++ class ObjectMonitor. It contains fields such as _owner, _recursions, and queues for waiting threads ( _cxq and _EntryList).
Acquiring a heavyweight lock executes ObjectMonitor::enter. The thread attempts a CAS on _owner; success means the lock is obtained, and the recursion count is incremented for re‑entrancy.
If the CAS fails, the thread enters a loop that may perform adaptive spinning ( TrySpin_VaryDuration) before eventually creating an ObjectWaiter and enqueuing itself in _cxq. When the queue is full or spinning fails, the thread blocks via pthread_mutex_lock.
Releasing the lock calls ObjectMonitor::exit. The recursion count is decremented; when it reaches zero, the monitor clears _owner and wakes up waiting threads according to the queue mode (QMode 2, 3, 4, etc.).
Wait and Notify
Calling Object.wait() moves the current thread into the _waitSet (a double‑linked list) and then releases the monitor via ObjectMonitor::exit. notify and notifyAll remove nodes from _waitSet and place them back into the appropriate waiting queue, waking them for the next lock acquisition attempt.
Adaptive Spinning
To avoid the overhead of immediate blocking, the JVM employs adaptive spinning: a thread that fails to acquire a heavyweight lock will spin for a short, dynamically‑adjusted duration (implemented in TrySpin and TrySpin_VaryDuration). If the lock becomes free during this spin, the thread can acquire it without a context switch.
Lightweight Lock
When contention is low, the JVM prefers a lightweight lock. The lock record ( LockRecord) is allocated on the current stack frame, and the object's MarkWord is copied there (the “displaced header”). A CAS then points the object's header to this LockRecord. If the CAS succeeds, the thread holds the lightweight lock; re‑entrancy is indicated by a null dhw field.
Unlocking a lightweight lock restores the original MarkWord from the stack via CAS. If the CAS fails because another thread has contested the lock, the lock inflates to a heavyweight lock.
Biased Lock
For the common case where a single thread repeatedly acquires the same lock, the JVM uses biased locking. The thread’s ID is stored in the MarkWord with the bias flag (bits 101). Subsequent lock acquisitions simply check the flag and the stored thread ID, avoiding any CAS.
If contention appears, the biased lock is revoked and the lock upgrades to a lightweight (or heavyweight) lock. The JVM tracks revocation counts per class (e.g., BiasedLockingBulkRebiasThreshold and BiasedLockingBulkRevokeThreshold) and may disable biased locking for a class after many revocations.
Summary
The synchronized mechanism in HotSpot combines several lock states—biased, lightweight, heavyweight—and uses CAS, adaptive spinning, and OS mutexes to balance performance and correctness. Understanding the monitor object, the MarkWord layout, and the lock‑inflation path provides deep insight into Java concurrency and helps answer interview questions about JVM internals.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
