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
# 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.
- duplicates
-
JDK-8318893 Bad performance of VarHandles
- Closed
- relates to
-
JDK-8058164 final fields in objects need to support inlining optimizations
- Open
-
JDK-8235844 Non-constant memory segments are never treated as loop invariants
- Open
-
JDK-8132243 Optimize Final Field Loads In Generated Code
- Draft
-
JDK-8294215 CallSiteTargetSelf microbenchmarks regression due changes in nmethod unloading behavior
- Open
-
JDK-5044412 (reflect) if setAccessible(true) has succeeded, allow final fields to be set
- Resolved
-
JDK-6912065 final fields in objects need to support inlining optimizations for JSR 292
- Resolved
-
JDK-8234401 ConstantCallSite may stuck in non-frozen state
- Resolved
-
JDK-8235602 Re-examine if a hidden class should trust final non static fields
- Resolved
-
JDK-8334754 C2: Optimize accesses to provably final instance fields
- Open
-
JDK-8024042 Add verification support for @Stable into VM
- Open
-
JDK-8134758 Final String field values should be trusted as stable
- Resolved
-
JDK-8140483 Atomic*FieldUpdaters final fields should be trusted
- Resolved
-
JDK-8161245 Enums should be fully optimized
- Closed