Detailed Explanation of Object Monitor (Monitor) and Synchronization Primitive Synchronized Underlying Implementation in Java
1. Knowledge Description
In Java concurrent programming, the synchronized keyword is the core mechanism for achieving thread synchronization and ensuring data consistency. The underlying core concept is the Object Monitor, which is a classic thread synchronization tool. Simply put, each Java object is associated with a monitor at the JVM level. synchronized achieves mutually exclusive access to critical sections (synchronized code blocks/methods) precisely by acquiring and releasing this monitor. Understanding the working mechanism of the object monitor is key to gaining a deep understanding of Java synchronization principles.
2. Step-by-Step Problem Solving/Explanation Process
Step One: Basic Association Between Object Header and Monitor
- Object Memory Layout: In the HotSpot JVM, an object's storage layout in heap memory is divided into three parts: object header, instance data, and alignment padding.
- Object Header is Key: The object header is the primary structure carrying the object monitor information. It mainly consists of two parts:
- Mark Word: Used to store the object's own runtime data, such as hash code, GC generation age, lock status flags, etc. It is also the key to implementing the lock (monitor).
- Klass Pointer: A pointer to the object's metadata (i.e., its class).
- Lock State: The flag bits (Lock Bits) in the Mark Word indicate the object's lock state. In a 64-bit JVM, the Mark Word is typically 64 bits, and its stored content changes according to the lock state. Main states include: unlocked (01), biased lock (01), lightweight lock (00), heavyweight lock (10), and GC mark (11), among others.
Step Two: How an Object Associates with the Real Monitor
- Heavyweight Lock's Monitor: When we refer to the classic "Object Monitor," we usually specifically mean the synchronization mechanism in the heavyweight lock state. It is a complex synchronization structure built from operating system-provided primitives like mutexes and condition variables.
- Association Process: When a thread attempts to acquire an object's lock (enter a
synchronizedblock) and competition escalates to a heavyweight lock, the JVM creates anObjectMonitorobject (implemented in C++) in the heap. ThisObjectMonitoris the concrete implementation of the Monitor mechanism. - Pointer Storage: At this point, the object's Mark Word no longer stores the original hash code or age information but is replaced by a pointer to this
ObjectMonitorobject. This operation is completed through a CAS (Compare and Swap) atomic operation. From then on, this Java object is bound to a heavyweight Monitor.
Step Three: Classic ObjectMonitor Structure and Its Working Principle
The core structure and work queues of the heavyweight lock's ObjectMonitor (in HotSpot source code: ObjectMonitor.hpp) are as follows:
_owner: Points to the thread holding this monitor (lock). InitiallyNULL._WaitSet: Wait set. Threads that call thewait()method are placed in this queue. These threads are waiting for a certain condition (triggered bynotify()/notifyAll())._EntryList: Entry list. Threads that fail to compete for the lock (blocking at thesynchronizedentry) are placed in this queue._recursions: Lock reentry count. Becausesynchronizedis reentrant.
Its workflow (simplified) is as follows:
- Thread Attempts to Enter (ENTER): When thread T1 wants to enter a code block protected by
synchronized(obj), it first attempts to atomically set the_ownerfield of theObjectMonitorassociated withobjto point to itself. - Successful Acquisition: If
_ownerisNULL, the setting succeeds, T1 acquires the lock, enters the synchronized code block to execute, and sets_recursionsto 1. - Reentry: If
_owneris already T1 itself,_recursionsis incremented by 1, reflecting reentrancy. - Competition Failure: If
_owneris another thread T2, then T1's acquisition attempt fails. - Entering EntryList: T1 will spin (early stage) or directly enter the
_EntryListqueue to block and wait. Under a heavyweight lock, this blocking is implemented by the operating system kernel's mutex, involving a switch from user mode to kernel mode, which is costly. - Releasing the Lock (EXIT): When the thread holding the lock, T2, exits the synchronized code block, it decrements
_recursionsby 1. If_recursionsbecomes 0, it sets_ownertoNULL. - Waking Up Competing Threads: T2 will wake up one or more waiting threads from the
_EntryListor_WaitSet(depending on different wake-up strategies). The awakened threads will re-attempt to compete for the lock (setting_owner).
Step Four: The Complete Path from the Synchronized Keyword to the Monitor
The execution of a synchronized method at the bytecode and JVM execution levels is as follows:
- Bytecode Instructions: For synchronized code blocks,
monitorenterandmonitorexitinstructions are generated after compilation. For synchronized methods, the method's access flagACC_SYNCHRONIZEDis set. - Interpreted Execution: When the interpreter executes the
monitorenterinstruction or enters anACC_SYNCHRONIZEDmethod, it calls the JVM's runtime function (e.g.,InterpreterRuntime::monitorenter). - Lock Upgrade Process: The JVM does not immediately use the heavyweight
ObjectMonitor. To optimize performance, it adopts a lock upgrade strategy:- Biased Lock: Assumes only one thread uses the lock. It records the thread ID in the Mark Word, making subsequent entries by that thread as fast as an unlocked state.
- Lightweight Lock: When there is slight contention, the thread creates a lock record (Lock Record) in its own stack frame and attempts to copy the Mark Word to the lock record and replace it with a pointer to the lock record via CAS. Success means acquiring the lock. This happens in user mode, avoiding kernel switches through spinning.
- Heavyweight Lock: When lightweight lock spinning fails (increased contention), the lock inflates. At this point, the JVM creates or associates the
ObjectMonitorand pushes the thread into_EntryListfor true blocking. This is the classic Monitor mechanism described in detail above.
- Final Association: In the heavyweight lock state, the mutual exclusion semantics of
synchronizedare guaranteed by thisObjectMonitorthrough the operating system's mutual exclusion primitives.
Summary:
The synchronization capability of synchronized is fundamentally based on the Monitor associated with each object. In scenarios with no or low contention, the JVM avoids the costly overhead of the Monitor through optimization techniques like biased locks and lightweight locks. In high-contention scenarios, the lock inflates to a heavyweight lock. At this point, the object points to an ObjectMonitor structure via its Mark Word. This structure, with its _owner, _EntryList, _WaitSet queues and with the support of the operating system kernel, implements mutual exclusion and conditional waiting between threads. Understanding this process reveals the complete path of synchronized from a syntax keyword to underlying system calls.