-
Bug
-
Resolution: Unresolved
-
P4
-
25
-
None
The first operation to trigger class initialization follows the entire process laid out in JVMS 5.5, including acquiring/releasing the initialization lock at designated points. Later, after initialization has completed, initialization-dependent operations are meant to check the status, see that the class is initialized, and proceed.
What is the memory model effect of these checks? If the first operation, (A), releases a lock in Step 10, and a subsequent operation in a different thread, (B), acquires the lock in Step 1, then (A)'s release in Step 10 happens-before (B). This is fine.
If a third operation in a third thread, (C), next acquires the lock in Step 1, then (A)'s release in Step 10 happens-before (C), but also everything in (B)'s thread up to (B) happens-before (C). This interpretation essentially forces a happens-before ordering between *every* initialization-dependent operation.
In practice, JVMs guarantee that the end of initialization happens-before all subsequent initialization-dependent operations, but do not enforce an ordering between other non-initializing threads. (If they did, then effectively every static field would have the same memory barrier overhead as a 'volatile' field.)
The end of 5.5 has this to say about optimizing the typical Step 1/Step 4 process followed by (B) and (C):
"A Java Virtual Machine implementation may optimize this procedure by eliding the lock acquisition in step 1 (and release in step 4/5) when it can determine that the initialization of the class has already completed, provided that, in terms of the Java memory model, all happens-before orderings (JLS §17.4.5) that would exist if the lock were acquired, still exist when the optimization is performed."
It should instead explain that the only happens-before orderings that need to be preserved are between Step 10 in the successful attempt, and Step 1 on subsequent attempts. It is *not* necessary to preserve happens-before orderings between one release in Step 4 and another acquisition in Step 1.
There's also some vague phrasing in the specs of the operations themselves, which calls into question whether each execution needs to engage in the 5.5 process at all. For example, from 'getstatic':
"On successful resolution of the field, the class or interface that declared the resolved field is initialized if that class or interface has not already been initialized (§5.5)."
This should be rephrased to ensure that the 5.5 process is applied each time (trivially in all but the first attempt). Otherwise, there's no basis for explaining how the initialization of a static field in <clinit> happens-before the read of that field in another thread, and if that weren't the case, you could end up with reads of default values.
What is the memory model effect of these checks? If the first operation, (A), releases a lock in Step 10, and a subsequent operation in a different thread, (B), acquires the lock in Step 1, then (A)'s release in Step 10 happens-before (B). This is fine.
If a third operation in a third thread, (C), next acquires the lock in Step 1, then (A)'s release in Step 10 happens-before (C), but also everything in (B)'s thread up to (B) happens-before (C). This interpretation essentially forces a happens-before ordering between *every* initialization-dependent operation.
In practice, JVMs guarantee that the end of initialization happens-before all subsequent initialization-dependent operations, but do not enforce an ordering between other non-initializing threads. (If they did, then effectively every static field would have the same memory barrier overhead as a 'volatile' field.)
The end of 5.5 has this to say about optimizing the typical Step 1/Step 4 process followed by (B) and (C):
"A Java Virtual Machine implementation may optimize this procedure by eliding the lock acquisition in step 1 (and release in step 4/5) when it can determine that the initialization of the class has already completed, provided that, in terms of the Java memory model, all happens-before orderings (JLS §17.4.5) that would exist if the lock were acquired, still exist when the optimization is performed."
It should instead explain that the only happens-before orderings that need to be preserved are between Step 10 in the successful attempt, and Step 1 on subsequent attempts. It is *not* necessary to preserve happens-before orderings between one release in Step 4 and another acquisition in Step 1.
There's also some vague phrasing in the specs of the operations themselves, which calls into question whether each execution needs to engage in the 5.5 process at all. For example, from 'getstatic':
"On successful resolution of the field, the class or interface that declared the resolved field is initialized if that class or interface has not already been initialized (§5.5)."
This should be rephrased to ensure that the 5.5 process is applied each time (trivially in all but the first attempt). Otherwise, there's no basis for explaining how the initialization of a static field in <clinit> happens-before the read of that field in another thread, and if that weren't the case, you could end up with reads of default values.
- relates to
-
JDK-8339207 Specifications for value classes and objects
-
- In Progress
-