Inside JVM: How synchronized Locks Work, From Biased to Heavyweight
This article explains the JVM's synchronized lock implementation, detailing where lock data is stored, the different lock states (no lock, biased, lightweight, heavyweight), the source code for lock acquisition and release, and the optimization strategies used to improve performance.
1. Storage and Classification of synchronized Locks
Storage location: Mark Word in the object header.
JVM Object Header information MarkWord: hashcode + age + biased_lock flag + lock flag Class Metadata Address Array Length (if the object is an array)
Lock types stored in MarkWord No lock: normal object (hashcode, age, no biased flag, lock flag) Lightweight lock: stack address + lock state Monitor lock: object/monitor address + lock state GC flag: GC link address + lock state Biased lock (JVM provided): thread ID + epoch + age + biased flag + lock state
2. synchronized Lock Acquisition (enter) Source Code
<code>// markWord.hpp
// 32‑bit MarkWord layout
// hash:25bit | age:4bit | biased_lock:1bit | lock:2bit (normal object)
// JavaThread*:23bit | epoch:2bit | age:4bit | biased_lock:1bit | lock:2bit (biased object)
// 64‑bit MarkWord layout
// unused:25bit | hash:31bit | unused_gap:1 | age:4bit | biased_lock:1bit | lock:2bit (normal object)
// JavaThread*:54bit | epoch:2bit | unused_gap:1 | age:4bit | biased_lock:1bit | lock:2bit (biased object)
// JVM lock state values
// [ptr | 00] locked // ptr points to real header on stack
// [header | 0 | 01] unlocked // regular object header
// [ptr | 10] monitor // inflated lock (header is swapped out)
// [ptr | 11] marked // used by mark‑sweep
// Current thread holds biased lock
// [JavaThread* | epoch | age | 1 | 01] lock is biased toward given thread
// Anonymous biased lock (no thread holds it)
// [0 | epoch | age | 1 | 01] lock is anonymously biased
</code>The JVM checks whether biased locking is enabled; if contention is detected it revokes the bias, otherwise it attempts to acquire a lightweight lock using a CAS on the object's MarkWord. If CAS fails, the lock is upgraded to a heavyweight monitor.
<code>static void enter(Handle obj, BasicLock* lock, TRAPS) {
if (UseBiasedLocking) {
if (!SafepointSynchronize::is_at_safepoint()) {
BiasedLocking::revoke(obj, THREAD);
} else {
BiasedLocking::revoke_at_safepoint(obj);
}
}
markWord mark = obj->mark();
assert(!mark.has_bias_pattern(), "should not see bias pattern here");
if (mark.is_neutral()) {
lock->set_displaced_header(mark);
if (mark == obj()->cas_set_mark(markWord::from_pointer(lock), mark)) {
return;
}
} else if (mark.has_locker() && THREAD->is_lock_owned((address)mark.locker())) {
assert(lock != mark.locker(), "must not re-lock the same lock");
assert(lock != (BasicLock*)obj->mark().value(), "don't relock with same BasicLock");
lock->set_displaced_header(markWord::from_pointer(NULL));
return;
}
// Upgrade to heavyweight lock
lock->set_displaced_header(markWord::unused_mark());
inflate(THREAD, obj(), inflate_cause_monitor_enter)->enter(THREAD);
}
</code>3. synchronized Unlock (exit) Source Code
<code>static void exit(oop obj, BasicLock* lock, Thread* THREAD);
void ObjectSynchronizer::exit(oop object, BasicLock* lock, TRAPS) {
// Handle biased lock revocation (already done during lock acquisition)
// Release lightweight lock
if (mark == markWord::from_pointer(lock)) {
assert(dhw.is_neutral(), "invariant");
if (object->cas_set_mark(dhw, mark) == mark) {
return;
}
}
// Release heavyweight lock via slow path
inflate(THREAD, object, inflate_cause_vm_internal)->exit(true, THREAD);
}
</code>4. Summary: JVM Optimization Strategies for synchronized
Optimization Goals
Improve response time
Increase throughput
Provide fast‑path lock acquisition (the “shortcut”) when possible
Optimization Techniques
Biased lock: used when there is no contention; only the MarkWord is modified, suitable for single‑thread or low‑contention scenarios.
Lightweight lock: activated when contention appears; uses a lock record on the stack and CAS spinning, reducing the cost compared to heavyweight locks.
Heavyweight lock: used after failed spinning; threads block, but the lock acquisition path is straightforward.
Spin lock: repeatedly performs CAS to avoid OS‑level blocking, though it consumes CPU and may suffer from the ABA problem.
Lock elimination: JIT compiler removes locks that are proven not to be shared, avoiding any locking overhead.
Thank you for reading; if you found this useful, feel free to share or like the article.
Xiaokun's Architecture Exploration Notes
10 years of backend architecture design | AI engineering infrastructure, storage architecture design, and performance optimization | Former senior developer at NetEase, Douyu, Inke, etc.
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.