Fundamentals 10 min read

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.

Xiaokun's Architecture Exploration Notes
Xiaokun's Architecture Exploration Notes
Xiaokun's Architecture Exploration Notes
Inside JVM: How synchronized Locks Work, From Biased to Heavyweight

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

MarkWord storage diagram
MarkWord storage diagram

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.

Biased lock flow diagram
Biased lock flow diagram
Lock upgrade flow diagram
Lock upgrade flow diagram

Thank you for reading; if you found this useful, feel free to share or like the article.

JVMLocksynchronizedJava Concurrencyheavyweight lockbiased locklightweight lock
Xiaokun's Architecture Exploration Notes
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.