Question 4 · Section 9

What is monitor in Java?

A monitor is a synchronization mechanism in Java that provides mutual exclusion. Every object in Java has its own built-in (intrinsic) monitor.

Language versions: English Russian Ukrainian

Junior Level

Basic Understanding

A monitor is a synchronization mechanism in Java that provides mutual exclusion. Every object in Java has its own built-in (intrinsic) monitor.

Intrinsic monitor — every Java object has a monitor “out of the box”. The JVM automatically associates a data structure for synchronization with each object. That’s why any object can be used as a lock key.

Simple Analogy

Imagine a meeting room with one door:

  • Monitor = the meeting room
  • Key = the lock
  • Queue outside = threads waiting to enter (EntryList)
  • Waiting room = threads that called wait() (WaitSet)

Usage Example

public class Counter {
    private int count = 0;

    // synchronized uses the monitor of object 'this'
    public synchronized void increment() {
        count++; // Only one thread can be here
    }

    public synchronized int getCount() {
        return count;
    }
}

Equivalent Forms

// Option 1: synchronized method
public synchronized void method() {
    // code
}

// Option 2: synchronized block (same thing)
public void method() {
    synchronized(this) { // Acquire monitor of object 'this'
        // code
    } // Release monitor
}

wait() and notify()

Monitor methods for inter-thread communication:

synchronized(lock) {
    while (!condition) {
        lock.wait(); // Releases monitor and waits
    }
    // Continues when notify() is called
}

// In another thread:
synchronized(lock) {
    condition = true;
    lock.notify(); // Wakes up one waiting thread
}

Important Rules

Rule Description
Only one owner Only one thread can own the monitor at a time
Reentrancy Owner can re-enter the monitor without blocking
wait() releases Calling wait() temporarily releases the monitor
Only inside synchronized wait()/notify() only work inside a synchronized block

Middle Level

Connection to Object Header (Mark Word)

Every object in Java has a header containing the Mark Word:

Mark Word — part of the object header in the JVM. Stores lock information: which thread owns it, state (unlocked, biased, thin, fat). On 64-bit JVM = 8-12 bytes.

|--------------------------------------------------------------|
| Object Header                                                  |
| -------------------------------------------------------------- | ------------------------- |
| Mark Word (64/32 bit)                                          | Class Pointer (64/32 bit) |
| -------------------------------------------------------------- |                           |

The Mark Word stores lock state bits:

Bits (64-bit JVM) State
01 No Lock / Biasable
01 Biased Locking (with Thread ID)
00 Lightweight Lock (Thin Lock)
10 Heavyweight Lock (Fat Lock)
11 Marked for GC

ObjectMonitor Structure (C++ JVM level)

When a monitor is “inflated” to heavyweight state, the Mark Word contains a pointer to ObjectMonitor:

// Simplified structure from HotSpot
class ObjectMonitor {
    void* _owner;          // Owner thread
    ObjectWaiter* _EntryList; // Queue of BLOCKED threads
    ObjectWaiter* _WaitSet;   // Queue of WAITING threads
    int _Recursion;           // Reentrancy counter
    int _WaitSetLock;         // WaitSet protection
};

Thread Lifecycle in a Monitor

                    ┌─────────────────────────────────┐
                    │                                 │
                    ▼                                 │
┌──────────┐  ┌──────────────┐  ┌──────────┐  ┌──────────────┐
│   NEW    │→│  EntryList   │→│  OWNER   │→│   EXIT       │
│ (created)│  │  (BLOCKED)   │  │(RUNNING) │  │ (released)   │
└──────────┘  └──────────────┘  └──────────┘  └──────────────┘
                                     │
                                     │ wait()
                                     ▼
                              ┌──────────────┐
                              │   WaitSet    │
                              │  (WAITING)   │
                              └──────────────┘
                                     │
                                     │ notify()
                                     ▼
                              ┌──────────────┐
                              │  EntryList   │
                              │  (BLOCKED)   │
                              └──────────────┘

Monitor Operations

Method Description
monitor.enter() Acquire monitor (blocks if busy)
monitor.exit() Release monitor
wait() Releases monitor and transitions to WAITING
notify() Moves one thread from WaitSet to EntryList
notifyAll() Moves all threads from WaitSet to EntryList

Lock Record on the Stack

Lock Record — a data structure on the thread’s stack that stores a copy of the Mark Word when acquiring a lock. Needed to restore the Mark Word on release.

For lightweight locks, the JVM avoids creating heavy ObjectMonitor:

  1. JVM copies the object’s Mark Word to the Lock Record on the thread’s stack
  2. Thread uses fast CAS operations to acquire
  3. If no contention — everything stays on the stack (very fast)
  4. If contention appears — Lock Inflation to Heavyweight occurs

Lock Inflation — transition from light locking (biased/thin) to heavy (fat lock). Happens under contention: when multiple threads try to acquire the same monitor.

When NOT to use monitors

  1. High-throughput, write-heavy: millions of ops/sec — lock-free algorithms (Atomic, CHM) are faster
  2. Read-heavy load: ReentrantReadWriteLock allows parallel reads, monitor does not
  3. Need tryLock with timeout: monitor blocks forever, ReentrantLock.tryLock(timeout) does not

Senior Level

Under the Hood: Lock State Evolution

The JVM (HotSpot) uses a lock escalation strategy (Lock Inflation):

1. No Lock (001)

|---------------------------------------------------------------|
| unused:25                                                       | identity_hashcode:31 | unused:1 | age:4 | bias:0 | 01  |
| --------------------------------------------------------------- | -------------------- | -------- | ----- | ------ | --- |

Object is not locked. Mark Word contains hash code and object age (GC age).

2. Biased Locking

|-------------------------------------------------------------------------|
| thread:54                                                                 | epoch:2 | unused:1 | age:4 | bias:1 | 01  |
| ------------------------------------------------------------------------- | ------- | -------- | ----- | ------ | --- |
  • If an object is only acquired by one thread, the JVM “biases” it toward that thread
  • Thread enters synchronized without CAS, just checking the thread ID in the header
  • Biased Locking — disabled by default since Java 15 (JEP 374), fully removed in Java 21 (JEP 416). In modern Java versions, monitoring goes: No Lock → Thin Lock → Fat Lock.

3. Thin Lock (lightweight lock)

|---------------------------------------------|
| thread:54                                     | lock record:6 | 00  |
| --------------------------------------------- | ------------- | --- |
  • Under contention, CAS is used to replace the Mark Word with a pointer to the Lock Record
  • Thread does not sleep — performs adaptive spinning (a few empty cycles)

4. Fat Lock (heavyweight lock)

|----------------------------------------------|
| object monitor pointer:62                      | 10  |
| ---------------------------------------------- | --- |
  • Under high contention, a full ObjectMonitor is created
  • Thread is parked via the OS (futex in Linux, WaitForSingleObject in Windows) — BLOCKED state
  • Context Switch — very expensive (~1000+ ns)

Adaptive Spinning

HotSpot JVM remembers lock history:

// JVM tracks:
// - How many times spinning succeeded on this monitor
// - How many times the thread parked immediately

// If spinning succeeded — spin longer
// If not — park the thread immediately

This dramatically improves performance for short-lived locks.

Monitor Inflation and Deflation

No Lock → Biased → Thin → Fat (Inflation — happens automatically)
Fat → Thin → ... (Deflation — only during Safe Points / GC)

Many Fat Locks → performance drops due to context switches.

JIT Compiler Optimizations

Lock Coarsening

// Before optimization:
synchronized(lock) { list.add(a); }
synchronized(lock) { list.add(b); }
synchronized(lock) { list.add(c); }

// After Lock Coarsening:
synchronized(lock) {
    list.add(a);
    list.add(b);
    list.add(c);
}

Lock Elision (via Escape Analysis)

public void method() {
    Object lock = new Object(); // Object doesn't "escape" the method
    synchronized(lock) {
        // JIT will completely remove this lock!
        // Because no one else can access lock
    }
}

Performance and Highload

Contention and EntryList

If hundreds of threads try to acquire one monitor:

  • EntryList becomes a bottleneck
  • Threads constantly context-switch
  • CPU spends more time switching than working

Solutions:

  • Segmented locks (like in ConcurrentHashMap)
  • Use LongAdder instead of AtomicInteger
  • ReadWriteLock when reads dominate writes

Diagnostics

jstack -l

jstack -l <pid>

Output:

"worker-1" #15 prio=5 os_prio=0 tid=0x00007f... nid=0x1234 waiting for monitor entry
  - waiting to lock <0x000000076af0c8d0> (a java.lang.Object)
  - locked <0x000000076af0c8e0> (a java.util.HashMap)

"worker-2" #16 prio=5 os_prio=0 tid=0x00007f... nid=0x1235 waiting for monitor entry
  - waiting to lock <0x000000076af0c8e0> (a java.util.HashMap)

JMC (Java Mission Control)

  • Event Java Monitor Wait — time in wait()
  • Event Java Monitor Enter — time in BLOCKED
  • Shows specific objects with the highest contention

javap -c

javap -c MyClass.class
  public void method();
    Code:
       0: aload_0
       1: dup
       2: astore_1
       3: monitorenter        // Acquire monitor
       4: ...
      20: aload_1
      21: monitorexit         // Normal exit
      22: goto 30
      25: astore_2
      26: aload_1
      27: monitorexit         // Exit on exception
      28: aload_2
      29: athrow
      30: return

Best Practices

  1. Minimize lock scope — keep synchronized blocks as short as possible
  2. Use private final Object lock — encapsulation, no external code can grab your lock
  3. Avoid class-level lockssynchronized(ClassName.class) is very dangerous
  4. Consider ReadWriteLock — if reading dominates writing
  5. Monitor contention via JMC — growing wait time = problem
  6. Avoid synchronized in high-load systems — prefer lock-free structures

Interview Cheat Sheet

Must know:

  • Every Java object has a built-in (intrinsic) monitor — a mutual exclusion mechanism
  • Monitor = EntryList (queue of BLOCKED threads) + WaitSet (queue of WAITING threads) + reentrancy counter
  • Mark Word in object header stores lock state: No Lock → Thin → Fat (escalation)
  • Biased Locking removed in Java 21; modern path: No Lock → Thin Lock → Fat Lock
  • wait() temporarily releases the monitor and moves the thread to WaitSet; notify() moves it to EntryList
  • JVM optimizes: Lock Coarsening (merging), Lock Elision (removal via Escape Analysis)
  • Under high contention, Thin Lock “inflates” to Fat Lock with ObjectMonitor (context switch ~1000+ ns)

Frequent follow-up questions:

  • What happens when synchronized is called? — JVM tries to acquire the monitor: first biased → thin (CAS + spinning) → fat (ObjectMonitor + OS parking)
  • Can you call wait() without synchronized? — No, IllegalStateException — wait/notify only work inside a synchronized block
  • What is a Lock Record? — A structure on the thread’s stack with a copy of the Mark Word; used in Thin Locking
  • When is a monitor NOT suitable? — High-throughput write-heavy loads; better to use lock-free (Atomic, ConcurrentHashMap)

Red flags (do NOT say):

  • “Monitor is a separate object in memory” — no, it’s built into every object (intrinsic)
  • “wait() releases the monitor forever” — no, temporarily; the thread returns to EntryList after notify()
  • “Synchronized always uses ObjectMonitor” — no, under low contention it uses thin lock on the stack
  • “Biased Locking is still relevant in Java 21” — no, fully removed (JEP 416)

Related topics:

  • [[5. How does synchronized work at monitor level]] — detailed monitor acquisition algorithm
  • [[6. What is the difference between synchronized method and synchronized block]] — different ways to use the monitor
  • [[7. What is reentrant lock]] — ReentrantLock as an alternative to the built-in monitor
  • [[1. What is the difference between synchronized and volatile]] — when you need a monitor vs volatile