rgoussu@goussu: ~/library/java/memory-and-gc
~/library/java/memory-and-gc cat java-memory-model.md

The Java Memory Model — happens-before & visibility

# The JMM contract — happens-before, volatile, final-field semantics and safe publication — that makes concurrent Java analyzable.

Conceptsaved 2026-08-09 #java#jvm#memory#concurrency#jmm

Overview

The Java Memory Model (JSR-133, JLS §17.4) is not about heap layout — it is the contract defining which writes a thread is guaranteed to see from other threads, given that compilers, JITs and CPUs all reorder aggressively. Its central bargain: write data-race-free programs and the JVM promises sequential consistency — execution behaves as if operations interleave in some global order. Race, and you get weakly defined behaviour: stale reads, half-published objects, impossible-looking states. The JMM is what makes concurrent Java analyzable rather than empirical.

Key points

  • Happens-before is the whole game: a write is visible to a read iff a happens-before (HB) chain connects them. HB is transitive; no chain, no guarantee — no matter how much wall-clock time passed.
  • The HB sources to memorize: program order within a thread · monitor unlock → later lock of the same monitor · volatile write → later read of the same variable · Thread.start() → everything in the started thread · everything in a thread → join() on it returning · Future.get(), and the documented HB edges of java.util.concurrent (queue put → take, executor submit → task run).
  • volatile = visibility + ordering, never atomicity: a volatile write publishes everything written before it to whoever reads the flag afterwards (piggybacking); but count++ on a volatile is still a lost-update race — that needs AtomicInteger/LongAdder or a lock.
  • final fields have their own rule: fields assigned in the constructor (with this not escaping) are visible fully initialized to every thread, even without synchronization — the guarantee immutable objects stand on, and why String can be shared freely.
  • Safe publication idioms: volatile/AtomicReference field · lock-guarded field · static final (class-init guarantee) · final field of a properly constructed object · handing off through a concurrent collection. Plain field assignment is not one — that's the double-checked-locking bug (fixed only by making the field volatile).
  • Stronger tools exist for weaker orderings: VarHandle (Java 9+) exposes acquire/release and opaque modes below volatile's full seq-cst — mechanical-sympathy territory; default to volatile and java.util.concurrent until profiling says otherwise.
  • Roads not taken: 100% single-threaded code needs none of this; but "it worked in the test" is the JMM's trap — racy code often runs correctly for months on x86 (strong hardware ordering) and breaks on ARM or after a JIT upgrade.

Examples

class Publisher {
    private int payload;                 // plain field
    private volatile boolean ready;      // the flag carries the HB edge

    void publish() { payload = 42; ready = true; }          // write payload BEFORE flag

    int consume() { return ready ? payload : -1; }          // reading true => sees 42
}
// Without volatile on `ready`, consume() may see ready==true and payload==0:
// both reorderings (write side and read side) are legal in a racy program.

The happens-before chain in that example, drawn out — transitivity is what connects the payload write to the payload read:

flowchart LR
    subgraph writer ["Writer thread"]
        W1["payload = 42"] -->|program order| W2["volatile write: ready = true"]
    end
    subgraph reader ["Reader thread"]
        R1["volatile read: ready == true"] -->|program order| R2["read payload: sees 42"]
    end
    W2 -->|volatile write happens-before later read| R1

Related

Citations

[1] JLS §17.4 — Memory Model [2] JSR-133 FAQ (Manson & Goetz) [3] Aleksey Shipilëv — Java Memory Model Pragmatics