Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8233873

final field values should be trusted as constant

XMLWordPrintable

    • Icon: Enhancement Enhancement
    • Resolution: Unresolved
    • Icon: P3 P3
    • tbd
    • 14
    • hotspot
    • None

      # 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.

            jrose John Rose
            jrose John Rose
            Votes:
            2 Vote for this issue
            Watchers:
            22 Start watching this issue

              Created:
              Updated: