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 volatile keyword. 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

2. Ordering Guarantee

JVM Implementation Enhancements

Modern JVMs (e.g., HotSpot) implement volatile behavior using memory barriers, such as:

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.

volatile does not guarantee atomicity. Use AtomicInteger, synchronized, or locks for compound actions like i++.

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.

Modern JVM Update: Most JVMs since JDK 8 guarantee atomic access to long and double even without volatile. 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