Understanding Java Memory Model (JMM) and volatile
This article provides a comprehensive guide to the Java Memory Model (JMM), including its design, the main-working memory model, memory operation instructions, and the semantics of the
volatilekeyword. Updated for 2025, it includes JVM-level enhancements and practical insights for concurrent programming.
1. Motivation: Consistent Memory Behavior Across Platforms
Java aims to be platform-independent, running seamlessly on various operating systems and hardware. However, differences in memory architecture, CPU cache design, and compiler optimizations pose challenges in multi-threaded environments.
To solve this, the Java Memory Model (JMM) defines how threads interact through memory, ensuring consistent behavior across platforms and processors.
2. Main Memory vs. Working Memory
JMM introduces a memory model that separates main memory from per-thread working memory. | Component | Description | |—————-|—————————————————————————–| | Main Memory | Shared by all threads; stores the master copy of variables. | | Working Memory | Thread-local; stores copies of variables used by the thread. | | Access Rule | Threads must read/write variables via their working memory, not directly from main memory. |
Any data exchange between threads must go through main memory.
3. The 8 Memory Operations in JMM
JMM defines 8 low-level operations that abstract how threads interact with memory.
| Operation | Meaning |
|————-|———————————————————–|
| lock | Mark a variable as exclusively accessible by a thread. |
| unlock | Release the exclusive access lock. |
| read | Read a variable from main memory into the working memory. |
| load | Load the variable into the working memory. |
| use | Use the variable in working memory. |
| assign | Assign a new value to the variable in working memory. |
| store | Write the value from working memory back to main memory. |
| write | Commit the stored value to main memory. |
These are conceptual operations—they guide how compilers and CPUs implement memory consistency, but are not directly exposed to developers.
4. Understanding volatile in JMM
The volatile keyword offers a lightweight synchronization mechanism in Java, ideal for sharing simple flags or state information between threads.
Key Characteristics of volatile
1. Visibility Guarantee
- A read of a
volatilevariable always fetches the latest value from main memory. - A write to a
volatilevariable is immediately flushed to main memory.
2. Ordering Guarantee
- Read and write operations on
volatilevariables are not reordered with other instructions. - This prevents subtle concurrency bugs caused by instruction reordering.
JVM Implementation Enhancements
Modern JVMs (e.g., HotSpot) implement volatile behavior using memory barriers, such as:
storestore: Prevents previous writes from being reordered after thevolatilewrite.storeload: Prevents subsequent reads from being reordered before thevolatilewrite.
These ensure both visibility and ordering across threads.
Performance Considerations
| Operation | Performance Impact |
|---|---|
| Read | Same as normal variable reads. |
| Write | Slightly slower due to memory barriers. |
| Compared to Locks | Much cheaper than using synchronized blocks. |
volatiledoes not guarantee atomicity. UseAtomicInteger,synchronized, or locks for compound actions likei++.
5. Non-Atomic Access to long and double (obsolete)
According to the Java specification, 64-bit types (long, double) may be accessed non-atomically on some 32-bit JVMs if not declared volatile.
- A thread might observe a half-updated value: one half old, one half new.
Modern JVM Update: Most JVMs since JDK 8 guarantee atomic access to
longanddoubleeven withoutvolatile. This issue is largely obsolete in current development environments.
6. The Happens-Before Principle
The Happens-Before principle is a core concept of JMM and is critical for reasoning about thread safety.
| Rule Name | Description | |
|---|---|---|
| 1 | Program Order Rule | Each action in a thread happens-before every subsequent action in that thread, as dictated by program control flow. |
| 2 | Monitor Lock Rule | An unlock on a monitor lock happens-before every subsequent lock on the same monitor. |
| 3 | Volatile Variable Rule | A write to a volatile variable happens-before every subsequent read of that same variable. |
| 4 | Thread Start Rule | A call to Thread.start() on a thread happens-before any action in the started thread. |
| 5 | Thread Termination Rule | Any action in a thread happens-before other threads detect that the thread has terminated (via Thread.join() or Thread.isAlive() returning false). |
| 6 | Thread Interruption Rule | A call to Thread.interrupt() happens-before the interrupted thread detects the interrupt (via isInterrupted() or interrupted()). |
| 7 | Finalizer Rule | The completion of an object’s constructor happens-before the start of its finalizer method. |
| 8 | Transitivity | If A happens-before B, and B happens-before C, then A happens-before C. |
If two operations are related by happens-before, their execution order is guaranteed and memory visibility is ensured. Otherwise, the JVM and CPU may reorder them, leading to potential data races.
7. Practical Use Cases
- Use
volatilefor flags and simple state sharing. - Avoid using
volatilefor compound operations (e.g., increments) without external synchronization. - Use
Atomic*classes for lock-free counters and accumulators. - Use
synchronizedfor complex atomic sections.