skip to content

Why does the Java Language Specification define a memory model at all? What would be left unspecified about a multi-threaded Java program if it did not?

level: middleimportance: must knowfreq 55%

answer

  1. Which writes may a read return
  2. javac + JIT + CPU all reorder
  3. as-if-serial protects one thread only
  4. abstract, not cache language, for portability
  5. minimum guarantees = optimization headroom

basics

~20 s

Because compilers and CPUs freely transform code, the language must state which value a read of a shared field may legally return. The memory model is that contract: it bounds optimization and reordering so one program behaves the same on every CPU.

solid answer

~50 s

A memory model formally answers one question: given a program, which writes may a particular read of a shared variable return? Without it, javac, the JIT and the processor could each transform the program as they like - hoisting a field into a register, reordering independent stores, buffering writes - and nothing would say where those transformations stop being legal. Single-threaded code is safe because every agent must preserve what one thread can observe; across threads the transformations become observable. Java needs this written down for two reasons. Portability: Java targets x86-64 (strongly ordered) as well as AArch64 and POWER (weakly ordered), so a spec written in terms of cache flushes would mean different semantics per machine. Optimization headroom: the spec states minimum guarantees rather than describing hardware. It has two audiences - JVM implementers, for whom it is an upper bound on optimization, and Java programmers, for whom it is the set of rules they may reason with.

go deeper

for a junior

Be able to say the memory model defines what a read of a shared variable may return, and that compilers and CPUs reorder code in ways only other threads can notice.

for a middle

Name the three agents that reorder (javac, JIT, CPU), explain as-if-serial, and explain why the spec is abstract rather than hardware-specific.

for a senior

Connect it to real failures: code that passes on x86 and breaks on AArch64, spin loops hoisted by the JIT, and why 'it worked in testing' is not evidence of correctness.

for a principal

Frame it as an interface contract between language, compiler and hardware - minimum guarantees maximize implementation freedom, and the cost is that correctness must be argued, not measured.

## The one question a memory model answers A memory model is the formal answer to: *given a program and a read of a shared variable, which writes may that read return?* Visibility, ordering and publication all fall out of that answer. Java's answer lives in chapter 17 of the Java Language Specification and is known as the Java Memory Model (JMM). ## Who is allowed to change your program Between the source line `int v = x;` and the value that lands in a register, several agents may transform the program: - **javac** does very little, but it may reorder within the limits of the language. - **The JIT compilers (C1, C2)** do a great deal: keep a field in a register across a loop, sink or hoist stores, eliminate reads it believes are redundant, reorder independent accesses. - **The processor** executes out of order and retires stores through a store buffer, so on weakly ordered machines other cores may observe stores in an order different from program order. Every one of these is constrained to preserve what a *single* thread could observe - the as-if-serial rule. That rule says nothing about a second thread. So the moment two threads touch the same field without synchronization, the transformations stop being invisible. ## Why the spec is abstract rather than about caches The JMM never mentions caches, store buffers or flushing. It is written in terms of actions, program order, synchronization order and a happens-before partial order over them. There are two reasons for that abstraction. **Portability.** Java runs on x86-64, where the hardware already orders most accesses, and on AArch64 and POWER, where it does not. If the language defined behaviour in hardware terms, the same program would have different legal outcomes per architecture and "write once, run anywhere" would fail exactly where it hurts most. Instead the JLS names the guarantees, and each JVM port emits whatever fences its hardware needs to meet them. **Optimization headroom.** A spec that guaranteed "every write is immediately visible" would forbid registers, forbid hoisting a field out of a loop, and force a fence after every store. By specifying only a minimum, the JMM lets the JIT optimize aggressively on data that is not shared - which is nearly all of it. ## What the spec deliberately does not promise - **No timing.** Nothing says how *soon* a write becomes visible. A racy spin on a non-volatile flag may legally never terminate, because the JIT can hoist the read out of the loop. - **No fairness or progress** of threads. - **Nothing about caches.** Modern cores are cache-coherent anyway; the reordering that hurts you comes from compilers and store buffers, not from stale caches. - **No safety net for racy programs** beyond one thing: even a badly synchronized program must not produce values out of thin air, so it can never violate type safety or forge a reference. ## Why it matters in practice Because the guarantees are minimums, a program can be incorrect under the model and still pass every test on your laptop - x86-64 hides most store/load reordering. The same binary on AArch64 (Graviton, Apple silicon) can expose it. The memory model is what lets you reason about correctness without running the program on every CPU you might deploy to.

  • The memory model never mentions cache flushing, yet people describe visibility as 'flushing caches'. Why is that description misleading?
    Mainstream CPUs are cache-coherent, so a committed store becomes visible to other cores without any explicit flush. The real sources of lost visibility are the compiler keeping a value in a register or eliminating a read, and the store buffer delaying when a store becomes globally visible. The model is written abstractly precisely so it covers both compiler and hardware effects rather than one hardware mechanism.
  • If a program is racy, does the memory model give it no guarantees at all?
    It gives weak but non-zero guarantees. Reads may return stale or surprising values, but the causality rules forbid out-of-thin-air values, so the JVM stays type-safe and memory-safe: a racy read can only return a value some write actually wrote. That is why a data race in Java is a correctness bug, not an exploitable memory-safety hole as it is in C++.

saying these in an interview costs you the question

  • Claiming the model exists because CPU caches are incoherent and need flushing
  • Saying the JVM guarantees writes become visible 'eventually' within some bounded time
  • Assuming that because a concurrent program works on x86 it is correct under the model
  • Treating the memory model as a runtime feature rather than a written specification that bounds implementations

context