skip to content

JSR-133 replaced Java's original memory model in Java 5. What was broken in the pre-Java-5 model, and what did the revision guarantee that the old one did not?

level: seniorimportance: should knowfreq 28%

answer

  1. Java 5 / JSR-133 rewrote JLS chapter 17
  2. old model: final fields guaranteed nothing
  3. old volatile ordered only volatile accesses
  4. DCL broken before, fixed with volatile after
  5. freeze at constructor end survives races

basics

~20 s

The old model was unimplementable and too weak: immutable objects with final fields could be seen partly built, volatile writes did not order surrounding normal writes, and double-checked locking was broken. JSR-133 added final-field freeze semantics and volatile ordering of nearby accesses.

solid answer

~50 s

The pre-Java-5 model, in chapter 17 of the earlier specification, had three practical defects. First, it gave **no guarantee for final fields**: a thread that obtained a reference to an immutable object through a race could observe its final fields with default values, so `String` immutability was not enforceable. Second, **volatile ordered only volatile accesses among themselves** - a volatile write did not stop surrounding normal writes from being reordered past it, so the double-checked-locking idiom was broken even with a volatile field. Third, the model was written so restrictively in places that common compiler optimizations were technically illegal, and so loosely in others that it permitted absurd executions; no JVM implemented it exactly. JSR-133, shipped with Java 5, rebuilt the model on happens-before plus causality rules. It added final-field freeze semantics at constructor end, strengthened volatile so a write orders prior normal writes and a read orders subsequent normal reads, fixed double-checked locking with a volatile field, and stated the SC-DRF guarantee explicitly.

code

java · 17 lines
java
class Holder {
    private static volatile Config instance;

    static Config get() {
        Config local = instance;          // one volatile read on the fast path
        if (local == null) {
            synchronized (Holder.class) {
                local = instance;
                if (local == null) {
                    local = new Config();  // pre-JSR-133: these writes could sink
                    instance = local;      // past the reference store
                }
            }
        }
        return local;
    }
}

go deeper

for a junior

Know that Java 5 changed the memory model, that volatile became stronger, and that final fields got a publication guarantee.

for a middle

Explain why double-checked locking was broken before Java 5 and what volatile now forbids from being reordered across it.

for a senior

Cover all three defects including implementability, and connect the final-field freeze to why immutable objects are safe to share and why constructor escape voids it.

for a principal

Frame it as a specification-design lesson: a model that is unimplementable or unreasonable gets ignored by both compilers and programmers, so the revision had to be simultaneously weak enough to implement and strong enough to build idioms on.

## Why the model needed replacing The memory model in the original Java specification was one of the few parts of Java everyone agreed was wrong. It was simultaneously too strong (forbidding optimizations every compiler already performed) and too weak (permitting executions no programmer could reason about), and it was ambiguous enough that no two JVMs implemented the same semantics. JSR-133, led by the concurrency community and delivered in Java 5 (2004), rewrote chapter 17 from scratch. The text in current specifications is that rewrite with editorial changes. ## Defect 1: final fields guaranteed nothing Under the old model, publishing an object through a data race could let another thread see its `final` fields at their default values. That is not an abstract concern: `java.lang.String` holds its contents in final fields, and security decisions are made on strings. If a string's fields could be observed half-built, a value could appear to change after being validated, which is a sandbox problem, not merely a correctness problem. JSR-133 introduced **freeze semantics**: the final fields of an object are frozen at the end of its constructor, and any thread that sees a reference to that object - even via a race - is guaranteed to see the frozen values, provided the reference did not escape the constructor. This is the only guarantee in the model that survives a data race, and it is why immutable objects are safe to share without synchronization. ## Defect 2: volatile was not enough for publication In the old model, volatile accesses were ordered relative to *other volatile accesses*, but ordinary reads and writes could be moved across them freely. So the classic double-checked-locking idiom was broken even when the field was declared volatile: the writes that initialize the new object could be reordered after the write of the reference, so another thread could take the fast path, see a non-null reference and use a half-initialized object. JSR-133 strengthened volatile into a genuine ordering point: a volatile write may not be reordered with earlier ordinary accesses, and a volatile read may not be reordered with later ordinary accesses. A volatile write now happens-before every subsequent read of that field, and everything the writer did before it is visible to the reader afterwards. Double-checked locking with a volatile field became correct in Java 5 and remains so. ## Defect 3: the model was not implementable Parts of the old text implied constraints that ruled out standard transformations - for example, certain reorderings a compiler performs on unshared data. Other parts admitted executions with self-justifying values. JSR-133 replaced the operational description with a definition built on actions, synchronization order, happens-before, and a set of causality rules constraining which executions are legal. It also stated the headline bargain explicitly: correctly synchronized programs have sequentially consistent semantics, and racy programs never see out-of-thin-air values. ## What else the revision settled - **The volatile-array trap remained:** declaring an array reference volatile orders access to the reference, never to the elements. JSR-133 did not change that, and it is still a favourite follow-up. - **Thread start/join edges** and the interaction of interruption and finalization were specified as happens-before edges. - **Word tearing for 64-bit values:** the carve-out permitting non-volatile `long` and `double` accesses to be split remained, but declaring the field volatile makes the access atomic. ## Why interviewers still ask The question separates candidates who learned rules from candidates who understand why the rules exist. The two takeaways that matter today: the safety of immutable objects is a *specification* guarantee that dates from Java 5 and depends on `this` not escaping the constructor, and the reason volatile is sufficient for publication is a change that was made deliberately, not a property volatile always had.

  • What condition must an object meet for the final-field guarantee to apply?
    The reference to the object must not escape during construction. If the constructor publishes 'this' - registering a listener, starting a thread, storing itself in a static - another thread can obtain the reference before the freeze at constructor end, and the guarantee is void. It also only covers fields declared final, and, for a final reference to a mutable object, only the reference and what was reachable from it at freeze time.
  • Does declaring an array reference volatile make writes to its elements volatile?
    No. The volatile qualifier applies to the field holding the reference, so reading or writing the reference has volatile semantics while element accesses remain plain. Publishing the array safely does not make later element updates visible; you need per-element atomic access or a lock for that.

saying these in an interview costs you the question

  • Claiming double-checked locking with a volatile field was already correct before Java 5
  • Believing final fields always had publication guarantees
  • Saying JSR-133 was only about volatile and ignoring final-field semantics
  • Asserting the final-field guarantee holds even when 'this' escapes the constructor

context