Java Memory Model (JMM)
AdvancedThe Java Memory Model defines when writes by one thread become visible to others, governing all concurrency guarantees in Java.
Overview
The Java Memory Model (JMM), defined in the Java Language Specification (JLS Chapter 17), specifies the rules by which threads interact through shared memory. Modern CPUs and compilers reorder instructions and cache values for performance. The JMM defines happens-before relationships that constrain this reordering and guarantee visibility. Understanding the JMM is essential for writing correct lock-free code and understanding why synchronized, volatile, and atomic operations work.
The Visibility Problem
Without synchronisation, a write by Thread A may never be seen by Thread B. This happens because CPUs have caches and write buffers; the JIT compiler may reorder or eliminate reads/writes.
The JMM allows all these optimisations unless a happens-before relationship exists between the write and the read.
// Classic visibility bug (may loop forever)
class BrokenStop {
static boolean stop = false; // no volatile
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (!stop) {} // JIT may hoist 'stop' read out of the loop
System.out.println("stopped");
}).start();
Thread.sleep(100);
stop = true; // write may never be flushed to main memory
// or the reader thread may never see it
}
}
// Why this can fail:
// 1. JIT compiler can cache 'stop' in a register (loop invariant hoisting)
// 2. CPU write buffer may not flush to main memory
// 3. Reader CPU cache may not invalidate its cached copy
// Fix: declare stop as volatile — establishes happens-beforeHappens-Before Rules
Happens-before (HB) is a formal ordering: if action A HB action B, then A's effects are guaranteed visible when B executes.
Key HB rules: 1. Program order: each action HB subsequent actions in the same thread. 2. Monitor: unlock HB subsequent lock of the same monitor. 3. Volatile: write to volatile HB all subsequent reads of that variable. 4. Thread start: Thread.start() HB any action in the started thread. 5. Thread join: all actions in T HB Thread.join(T) returning. 6. Transitivity: if A HB B and B HB C then A HB C.
// Happens-before through volatile
volatile int flag = 0;
int data = 0;
// Thread A
data = 42; // (1)
flag = 1; // (2) volatile write
// Thread B
if (flag == 1) { // (3) volatile read — HB guarantees (2) is visible
// (3) HB (2) because volatile read sees the volatile write
// (2) HB in program order, so (1) HB (2)
// Transitivity: (1) HB (3) → data is guaranteed to be 42
System.out.println(data); // ALWAYS 42, not 0
}
// Happens-before through synchronized
Object lock = new Object();
int value = 0;
// Thread A
synchronized(lock) { value = 100; } // unlock HB next lock
// Thread B
synchronized(lock) {
// lock HB unlock in Thread A → value is guaranteed 100
System.out.println(value);
}Safe Publication and Final Fields
An object is safely published when it is made visible to other threads in a way that guarantees all its fields are also visible (not just the reference).
Safe publication idioms: store in a volatile field, store in a final field, store via synchronized, store in a concurrent collection.
final fields have a special JMM guarantee: after a constructor completes, any thread that obtains a reference to the object is guaranteed to see all final fields as set by the constructor — without synchronisation.
// UNSAFE publication — other threads may see partial state
class UnsafeHolder {
public Object value;
public UnsafeHolder() { value = new HeavyObject(); }
}
static UnsafeHolder holder; // non-volatile, non-final
// Thread A: holder = new UnsafeHolder()
// Thread B: holder.value — may see null (JIT reordering of stores)
// SAFE publication via volatile
static volatile UnsafeHolder safeHolder;
// SAFE via final — JMM special guarantee
class ImmutablePoint {
final int x, y;
ImmutablePoint(int x, int y) { this.x = x; this.y = y; }
// Any thread seeing a reference to an ImmutablePoint is
// GUARANTEED to see the final fields as set in the constructor
}
// SAFE via static initialiser — class init is synchronized by JVM
static final ImmutablePoint ORIGIN = new ImmutablePoint(0, 0);
// SAFE via concurrent collection
ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();
map.put("key", new HeavyObject()); // publication is safeInteractive Visualization
Key Points to Remember
- JMM defines happens-before: if A HB B, all effects of A are visible when B executes.
- Volatile write HB subsequent volatile read — ensures visibility without locking.
- Synchronized unlock HB subsequent lock of the same monitor.
- final fields are safely published after constructor completes — no synchronisation needed.
- Safe publication: use volatile, final, synchronized, or concurrent collections to share objects.
Practice Java Memory Model (JMM) in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaWhat is the happens-before relationship in the Java Memory Model?
What guarantees does a final field provide across threads?
What is safe publication and why does it matter?
Why can the JIT reorder instructions and what prevents it from reordering across volatile?
What is the difference between visibility and atomicity in Java concurrency?
Ask Aria about Java Memory Model (JMM)
Your personal AI tutor — ask anything about this concept