Fundamentals 13 min read

Why Java’s final Fields Matter: Memory Model Rules Explained

This article explores Java’s memory model rules for final fields, detailing how writes and reads are ordered, the impact on object construction, examples with primitive and reference types, and processor-specific implementations, illustrating why final provides safe initialization without explicit synchronization.

Programmer DD
Programmer DD
Programmer DD
Why Java’s final Fields Matter: Memory Model Rules Explained

Understanding Java final fields in the Java Memory Model

Java language keywords are carefully designed; this article dives into the JMM rules for final fields.

Compared with lock and volatile, reads and writes of final fields behave like ordinary variable accesses, but the compiler and processor must obey two reordering rules:

Writing a final field inside a constructor and then assigning the constructed object’s reference to a variable cannot be reordered.

The first read of an object reference and the subsequent first read of its final field cannot be reordered.

public class FinalExample {
  int i; // ordinary variable
  final int j; // final variable
  static FinalExample obj;

  public FinalExample() {
    i = 1; // write ordinary field
    j = 2; // write final field
  }

  public static void writer() {
    obj = new FinalExample(); // thread A writes
  }

  public static void reader() {
    FinalExample object = obj; // thread B reads reference
    int a = object.i; // read ordinary field
    int b = object.j; // read final field
  }
}

Assume thread A executes writer() and then thread B executes reader(). The following sections illustrate the two rules with this example.

Write‑final‑field reordering rule

The rule prevents the write to a final field from being reordered outside the constructor. It is enforced in two ways:

The JMM forbids the compiler from moving a final‑field write outside the constructor.

The compiler inserts a StoreStore barrier after the final‑field write and before the constructor returns, preventing the processor from reordering the write.

In the diagram below, the write to the ordinary field i is reordered outside the constructor, causing thread B to read an uninitialized value, while the write to the final field j stays within the constructor, so thread B reads the correctly initialized value.

Read‑final‑field reordering rule

The rule ensures that the first read of an object reference and the first read of its final field are not reordered by the processor. The compiler inserts a LoadLoad barrier before the final‑field read.

In the diagram below, the read of the ordinary field is reordered before the reference read, leading to a stale value, while the read of the final field occurs after the reference read, guaranteeing a correctly initialized value.

# If a final field is a reference type

When the final field holds a reference, the write rule adds an extra constraint: the write to a member of the referenced object inside the constructor cannot be reordered with the assignment of the object’s reference to a variable.

public class FinalReferenceExample {
  final int[] intArray; // final reference
  static FinalReferenceExample obj;

  public FinalReferenceExample() {
    intArray = new int[1]; // 1
    intArray[0] = 1;        // 2
  }

  public static void writerOne() { // thread A
    obj = new FinalReferenceExample(); // 3
  }

  public static void writerTwo() { // thread B
    obj.intArray[0] = 2; // 4
  }

  public static void reader() { // thread C
    if (obj != null) {
      int temp1 = obj.intArray[0]; // 6
    }
  }
}

The JMM guarantees that thread C will at least see the write performed in the constructor (value 1). The write performed by thread B (value 2) may or may not be visible because there is a data race.

# Why a final reference cannot “escape” from the constructor

If the constructor publishes this (e.g., assigning it to a static field), the object’s reference may become visible before the final field is initialized, breaking the guarantee.

public class FinalReferenceEscapeExample {
  final int i;
  static FinalReferenceEscapeExample obj;

  public FinalReferenceEscapeExample() {
    i = 1;          // 1 write final field
    obj = this;     // 2 "this" escapes
  }

  public static void writer() {
    new FinalReferenceEscapeExample();
  }

  public static void reader() {
    if (obj != null) {
      int temp = obj.i; // 4 read final field
    }
  }
}

Because the reference can be observed before the constructor finishes, another thread may read the default value of i (0) instead of the initialized value (1).

# Implementation of final semantics on processors

On x86, the processor does not reorder write‑write or dependent reads, so the StoreStore and LoadLoad barriers required by the JMM are effectively omitted. Consequently, final field reads and writes on x86 do not incur extra memory‑barrier instructions.

Why JSR‑133 enhanced final semantics

The original Java memory model allowed a thread to observe a final field’s default value and later see the initialized value, breaking immutability guarantees (e.g., a String could appear to change). JSR‑133 introduced the write and read reordering rules for final fields, giving programmers safe initialization without needing explicit synchronization, provided the object does not escape during construction.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaMemory ModelJMMfinal
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.