JDK-8366372 : Valhalla: memory model rules for strict instance fields
  • Type: Bug
  • Component: specification
  • Sub-Component: language
  • Priority: P2
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2025-08-28
  • Updated: 2025-09-26
  • Resolved: 2025-09-26
Related Reports
Blocks :  
Blocks :  
Blocks :  
Relates :  
Sub Tasks
JDK-8367767 :  
JDK-8367785 :  
Description
Final instance fields have special memory model rules that apply "when determining which values can be seen by" a read operation. These rules ensure that, for an object that was properly shared with the current thread after a "freeze" action at the end of a constructor, values that were held earlier by the field, but were overwritten before the "freeze", cannot be observed. Most commonly, this means that the read cannot observe the final field's default value.

The same principle should apply to all strictly-initialized instance fields (final and non-final, including the fields of value classes): any values of the field that were overwritten before a "freeze" action cannot be observed by reads in other threads.

Two differences from non-strict final fields:

- The "freeze" action happens at the point of a 'super()' call (or equivalently, on entry to the Object superconstructor)

- The object will *always* be "properly shared" with other threads, because it cannot be shared before the "freeze" action.

The specification for the final field behavior today (17.5.1) is messy and appears to be buggy. One move I think would be helpful is to abandon the idea of defining a new happens-before edge that "does not transitively close with other happens-before orderings". It doesn't really make sense to frame it in this way, and the new edges should just be given a different name. These new edges only seem to impact the "allowed to observe" relation defined in 17.4.5, so there's room for some renaming/refactoring here.

Fortunately, the complex rules in 17.5.1 about when an object is "properly shared" are not relevant to strict fields, and can probably be left alone.
Comments
Test case to consider: abstract value class A { final int x; A() { x = 1; super(); shareWithOtherThread(this); } } class B extends A { final int y; final int z; B() { y = 2; super(); z = 3; } } Per these rules, A.x is frozen at the 'super()' call and must never be observable as '0'. But B.y and B.z are not frozen until the end of the B constructor, and so the concurrent thread may be allowed to observe both B.y and B.z as '0'. (In practice, it may be convenient for a memory barrier to apply to *both* x and y, and so only z could be observed as '0'. But the specified requirement only applies to x.)
22-09-2025

Proposed change to 17.5.1: Let o be an object, and c be a constructor for o in which a final field f is written. A freeze action on final field f of o takes place ~~when c exits, either normally or abruptly.~~ **as follows:** **In a value class, the freeze action occurs immediately before the constructor invocation in c transfers control to another constructor.** **In an identity class, the freeze action occurs when c exits, either normally or abruptly.** Note that if one constructor invokes another constructor, and the invoked constructor sets a final field, the freeze for the final field takes place at the end of the invoked constructor. For each execution, the behavior of reads is influenced by two additional partial orders... ----- Some notes: - While these rules uniformly attach the freeze to a constructor that performs a field write, in practice it is impossible to observe the written field until after execution reaches Object.<init>, so an implementation can collapse all of the specified early freeze actions into one action for all of an object's strict instance fields at Object.<init>. - The JLS is concerned with the semantics of Java programs, so we do not address here other kinds of strict fields (both final and non-final) that can only be expressed in bytecode. But the intent is that the timing of a freeze action for one of these fields would be the same as in a value class. - There will be more to say/revise about initialization of null-checked fields and arrays, in order to provide the necessary integrity constraints. I've worked on some of that, but wasn't happy with the result and think it will take some deeper changes. (For example, the idea that every variable gets an initializing write of null/zero is pretty baked in to the memory model today, and closely relates to the idea of a freeze action.) - As a pure cleanup task, I am uncomfortable with the way this section introduces a happens-before ordering that "does not transitively close with other happens-before orderings." I may try to adjust the terminology to preserve this idea without polluting what "happens-before" means.
19-09-2025

Should this belong to Strict Field Initialization JEP instead of Value Objects JEP?
28-08-2025