Other |
---|
tbdUnresolved |
Duplicate :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
|
Relates :
|
# Problem The JVM JITs routinely optimize references to final fields as constant values, when a JIT can deduce a constant containing object. This is a fundamental capability for producing good code. Currently, though, only a small number of "white listed" fields are treated in this way, since vigorously optimizing _all_ final fields is thought to have unknown risky consequences. The white listing logic is defined using the function `trust_final_non_static_fields` and similar logic as part of changes like JDK-6912065 and JDK-8140483. # Proposal The JVM should support an option `FoldConstantFields` which treats bypasses the above "white list" and uses a "black list" instead as needed. Initially this option should be turned off by default. Turning it on should, initially, also turn on a new option `VerifyConstantFields` which detects updates to final fields and diagnoses them with some selectable mix of warnings or errors. (See below for discussion of how updates to final fields can occcur. The short summary is "reflection, JNI, or Unsafe". Each of these requires a different remediation.) This feature will not solve the problem of full optimization of constant fields all at once, but will set the stage for finding and fixing problems caused by such optimizations. The support for `FoldConstantFields` should include (either initially or as follow-on work) the following functions: - Dependency recording in the JIT, whenever a final field value is used. At first this should be recorded per field declaration, not per individual field instance, on the assumption that invalidation will be very rare. This assumption may need to be revised. - Updates to final fields via reflection must be trapped and must trigger deoptimization of dependent JIT. - Updates to final fields via JNI must be trapped similarly. - Updates to final fields via other users of `Unsafe` must be trapped similarly. This addresses uses of `Unsafe` _that the JDK knows about and controls_. - Encourage other users of `Unsafe` to perform similar notifications, and document how to do so. Perhaps there are additional `Unsafe` API points to notify the JIT. - Placing the checking logic inside `Unsafe` is the wrong answer in most cases, since it would penalize well-behaved users of `Unsafe`. Perhaps a separate flag `VerifyUnsafeUpdates` would be applicable, for stress tests where performance can be sacrificed. - Define an API for use by privileged frameworks (including those in the JDK) for creating objects in a "larval" state, apart from normal constructor invocation. (Possibly `Unsafe.allocateInstance` is such an API point; see also JNI AllocObject.) These are released from the constraints on final field writing, including JIT invalidation. If a JIT encounters an object in the larval state, the JIT will simply refrain from constant-folding its fields. - Define an API for promoting larval objects to a normal "adult" state, at which point the normal JIT optimizations would apply. If this isn't done, performance will be lost only regarding the larval objects created by old frameworks, so perhaps this isn't needed. - It seems likely that the larval and adult states would need to be reflected in a bit pattern in the object header. As an optimization, normally constructed objects would probably not need to have this state change in their header bits, unless perhaps they "escape" during their constructor call. # Discussion A final field can in some cases be assigned a new value. If a JIT has already observed the previous value of that final field, and incorporated it into object code as a constant, then (after the assignment of a new value to that field), the optimized object code will execute wrongly. We call such wrongly executing code "invalid", and the JVM takes great care to avoid executing invalid code in similar cases involving speculative optimizations, such as devirtualized method calls or uncommon traps. The basic reason for this is that the Java Memory Model requires that all fields (including changed final fields) must be read accurately. An accurate read yields a value that is appropriate to the current thread, as defined by a web of "happens-before" relations. (It is not entirely wrong to think of these relations as a linear set, although concurrency and races are also part of the JMM.) But final fields _must_ be changed when an object is initialized, and _may rarely_ change in other circumstances. There are a number of ways to change the current value of a final field: 0. In a constructor, a final field may be changed from its current value (typically initial default value) to a new (possibly non-default) value. The JVM (per specification) allows this to occur _multiple times_ although most sources of bytecode are thought to avoid such behavior. 1. When a field is reflected, and `setAccessible(true)` is called, the value may be set. This "hook" is intended for use by deserializers and other low-level facilities. It is thought to be used as a simulation of case #0 above, when an object's constructor cannot be conveniently invoked. In a real sense, holding this option open for serialization frameworks harms the optimization of the entire ecosystem. 2. JNI functions such as SetBooleanField can be used to smash new values into fields even if they are final. 3. Good old `Unsafe.setInt` can be also be used to smash new values into fields (or parts of fields or groups of fields) even if they are final. Although a debugger can forcibly change the value of a field from outside the JVM, via APIs in the `jdk.jdi` module, it appears to be impossible to use those APIs to change final fields. It is unknown what libraries or bytecode spinners "in the wild" are using any of the four options above in ways that would invalidate JIT-compiled code. Setting the JITs free to optimize fully requires a plan for mitigating the impact of final field changes both in known code (in the JDK) and in unknown "wild" code. ## Side note on races Although race conditions (on non-volatile fields) allow the JVM some latitute to return "stale" values for field references, such latitude would usually be quite narrow, since an execution of the invalid optimized method is likely to occur downstream of the invalidating field update (as determined by the happens-before relation of the JMM). The JMM itself would have to be updated to either relax happens-before relations pertaining to final field updates, or else allow special race conditions that allow the JIT to use stale values of final fields (in effect, loo king backward in time, past events visible through the relevant happens-before events). There are no active proposals to update the JMM in this way, and it seems easier to take the JMM as a given, or (at most) make very small changes to it to further specialize the treatment of final fields.
|