Object Memory Layout in Java and the Synchronized Lock Upgrade Process
I. Knowledge Description
In Java, each object has a specific layout structure in memory, and the lock mechanism implemented by the synchronized keyword dynamically upgrades based on contention. Understanding the object memory layout is fundamental to mastering the synchronized lock upgrade process.
II. Detailed Object Memory Layout
-
Ordinary Object Structure (64-bit VM with pointer compression enabled):
- Mark Word (8 bytes): Stores hash code, GC age, lock status, etc.
- Klass Pointer (4 bytes): Pointer to class metadata.
- Instance Data (variable length): The actual field data of the object.
- Alignment Padding (optional): Ensures the object size is a multiple of 8 bytes.
-
Special Structure for Array Objects:
- Mark Word (8 bytes)
- Klass Pointer (4 bytes)
- Array Length (4 bytes)
- Array Data (variable length)
III. Detailed Structure of Mark Word
Taking a 64-bit VM as an example, the structure of Mark Word under different lock states:
-
Lock-Free State (Lock flag bits 01):
- 25 bits: Unused
- 31 bits: Object's hashCode
- 1 bit: Unused
- 4 bits: Object generational age
- 1 bit: Biased mode (0)
- 2 bits: Lock flag (01)
-
Biased Lock (Lock flag bits 01):
- 54 bits: Thread ID holding the biased lock
- 2 bits: Epoch (timestamp)
- 1 bit: Unused
- 4 bits: Object generational age
- 1 bit: Biased mode (1)
- 2 bits: Lock flag (01)
-
Lightweight Lock (Lock flag bits 00):
- 62 bits: Pointer to lock record in the stack
- 2 bits: Lock flag (00)
-
Heavyweight Lock (Lock flag bits 10):
- 62 bits: Pointer to the monitor (mutex)
- 2 bits: Lock flag (10)
IV. Synchronized Lock Upgrade Process
-
Lock-Free State
- The object is in a lock-free state immediately after creation.
- Lock upgrade begins when the first thread accesses a synchronized block.
-
Biased Lock Phase
- Trigger Condition: No contention, only one thread accesses.
- Upgrade Process:
a. Check if the thread ID in Mark Word points to the current thread.
b. If yes, acquire the lock directly (reentrancy).
c. If not, attempt to replace the thread ID with the current thread's ID via CAS.
d. Success means acquiring the biased lock; failure leads to upgrade to a lightweight lock. - Advantage: Eliminates synchronization overhead, suitable for scenarios with only one accessing thread.
-
Lightweight Lock Phase
- Trigger Condition: Slight contention, multiple threads execute alternately.
- Upgrade Process:
a. Create a Lock Record in the current thread's stack frame.
b. Copy the object header's Mark Word to the Lock Record (Displaced Mark Word).
c. Use CAS to replace the object header's Mark Word with a pointer to the Lock Record.
d. Success means acquiring the lightweight lock; failure leads to spin retry or upgrade to a heavyweight lock. - Spin Optimization: The failing thread spins for a limited number of times (default 10) to avoid immediate blocking.
-
Heavyweight Lock Phase
- Trigger Condition: Intense contention, spin reaches threshold, or multiple threads compete simultaneously.
- Upgrade Process:
a. Request a mutex from the operating system.
b. Point the Mark Word to the mutex.
c. Threads failing the competition enter a blocking queue, scheduled by the OS. - Characteristics: A true mutex lock, involving a switch from user mode to kernel mode.
V. Complete Lock Upgrade Flowchart
Lock-Free → (First thread accesses) → Biased Lock → (Second thread contends) → Lightweight Lock → (Intense contention / Spin limit exceeded) → Heavyweight Lock
VI. Lock Degradation Mechanism
- Normally, locks can only be upgraded, not downgraded.
- Under specific conditions (e.g., during GC), heavyweight locks may be downgraded.
- Biased locks may have their bias revoked under certain conditions.
VII. Practical Verification Example
public class LockUpgradeDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// Initial state: Lock-free
System.out.println("Initial state: Lock-free");
// First thread acquires lock, upgrades to biased lock
new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 1 acquired lock - Biased lock state");
}
}).start();
Thread.sleep(1000);
// Second thread competes, upgrades to lightweight lock
new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 2 acquired lock - Lightweight lock state");
}
}).start();
Thread.sleep(1000);
// Multiple threads compete intensely, upgrade to heavyweight lock
for (int i = 0; i < 10; i++) {
new Thread(() -> {
synchronized (lock) {
System.out.println("Thread " + Thread.currentThread().getId() + " acquired lock");
}
}).start();
}
}
}
VIII. Performance Optimization Suggestions
- Reduce lock granularity, avoid unnecessary synchronization.
- For read-heavy, write-light scenarios, consider using read-write locks.
- Set spin count reasonably to avoid CPU idle spinning.
- In scenarios where contention is clearly absent, biased locking can be disabled via JVM parameters (
-XX:-UseBiasedLocking).
By understanding object memory layout and the lock upgrade process, you can better optimize the performance of synchronized code and avoid unnecessary lock contention.