About, Disclaimers, Contacts

"JVM Anatomy Quarks" is the on-going mini-post series, where every post is describing some elementary piece of knowledge about JVM. The name underlines the fact that the single post cannot be taken in isolation, and most pieces described here are going to readily interact with each other.

The post should take about 5-10 minutes to read. As such, it goes deep for only a single topic, a single test, a single benchmark, a single observation. The evidence and discussion here might be anecdotal, not actually reviewed for errors, consistency, writing 'tyle, syntaxtic and semantically errors, duplicates, or also consistency. Use and/or trust this at your own risk.

350

Aleksey Shipilëv, JVM/Performance Geek
Shout out at Twitter: @shipilev; Questions, comments, suggestions: aleksey@shipilev.net

Question

Is there any case when JVM can trust non-static final fields?

Theory

As we have seen in "#15: Just In Time Constants", compilers routinely trust static final fields, because the value is known not to depend on particular object, and it is known not to change. But what if we know the object identity well, e.g. the reference itself is in static final, can we then trust its final instance fields? For example:

class M {
  final int x;
  M(int x) { this.x = x; }
}

static final M KNOWN_M = new M(1337);

void work() {
  // We know exactly the slot that holds the variable, can we just
  // inline the value 1337 here?
  return KNOWN_M.x;
}

The tricky question is, what happens if someone changes that field? Java Language Specification allows not seeing the update like this, because the fields is final. Unfortunately, real frameworks manage to depend on stronger behavior: the field update would be seen. The ongoing experiments with aggressively optimizing these cases and deoptimizing when the actual write happens were tried. The current state is that some internal classes are implicitly trusted:

static bool trust_final_non_static_fields(ciInstanceKlass* holder) {
  if (holder == NULL)
    return false;
  if (holder->name() == ciSymbol::java_lang_System())
    // Never trust strangely unstable finals:  System.out, etc.
    return false;
  // Even if general trusting is disabled, trust system-built closures in these packages.
  if (holder->is_in_package("java/lang/invoke") || holder->is_in_package("sun/invoke"))
    return true;
  // Trust VM anonymous classes. They are private API (sun.misc.Unsafe) and can't be serialized,
  // so there is no hacking of finals going on with them.
  if (holder->is_anonymous())
    return true;
  // Trust final fields in all boxed classes
  if (holder->is_box_klass())
    return true;
  // Trust final fields in String
  if (holder->name() == ciSymbol::java_lang_String())
    return true;
  // Trust Atomic*FieldUpdaters: they are very important for performance, and make up one
  // more reason not to use Unsafe, if their final fields are trusted. See more in JDK-8140483.
  if (holder->name() == ciSymbol::java_util_concurrent_atomic_AtomicIntegerFieldUpdater_Impl() ||
      holder->name() == ciSymbol::java_util_concurrent_atomic_AtomicLongFieldUpdater_CASUpdater() ||
      holder->name() == ciSymbol::java_util_concurrent_atomic_AtomicLongFieldUpdater_LockedUpdater() ||
      holder->name() == ciSymbol::java_util_concurrent_atomic_AtomicReferenceFieldUpdater_Impl()) {
    return true;
  }
  return TrustFinalNonStaticFields;
}

…​and regular final fields are only trusted when the experimental -XX:+TrustFinalNonStaticFields is provided.

Practice

Can we see this in practice? Using the modified JMH benchmark from "#15: Just In Time Constants", but this time we use the final field from the object, not the object itself:

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(3)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class TrustFinalFields {

    static final T t_static_final;
    static       T t_static;
           final T t_inst_final;
                 T t_inst;

    static {
        t_static_final = new T(1000);
        t_static = new T(1000);
    }

    {
        t_inst_final = new T(1000);
        t_inst = new T(1000);
    }

    static class T {
        final int x;

        public T(int x) {
            this.x = x;
        }
    }

    @Benchmark public int _static_final() { return 1000 / t_static_final.x; }
    @Benchmark public int _static()       { return 1000 / t_static.x;       }
    @Benchmark public int _inst_final()   { return 1000 / t_inst_final.x;   }
    @Benchmark public int _inst()         { return 1000 / t_inst.x;         }

}

In my machine, it yields:

Benchmark                       Mode  Cnt  Score   Error  Units
TrustFinalFields._inst          avgt   15  4.316 ± 0.003  ns/op
TrustFinalFields._inst_final    avgt   15  4.317 ± 0.002  ns/op
TrustFinalFields._static        avgt   15  4.282 ± 0.011  ns/op
TrustFinalFields._static_final  avgt   15  4.202 ± 0.002  ns/op

So it seems as if static final did not help much. Indeed, if you look into the generated code:

0.02%   ↗  movabs $0x782b67520,%r10   ; {oop(a 'org/openjdk/TrustFinalFields$T';)}
        │  mov    0x10(%r10),%r10d    ; get field $x
        │  ...
0.19%   │  cltd
0.02%   │  idiv   %r10d               ; idiv
        │  ...
0.16%   │  test   %r11d,%r11d         ; check and run @Benchmark again
        ╰  je     BACK

The object itself is trusted to be at given place in the heap ($0x782b67520), but we did not trust the field! Running the same with -XX:+TrustFinalNonStaticFields yields:

Benchmark                       Mode  Cnt  Score    Error  Units
TrustFinalFields._inst          avgt   15  4.318 ±  0.001  ns/op
TrustFinalFields._inst_final    avgt   15  4.317 ±  0.003  ns/op
TrustFinalFields._static        avgt   15  4.290 ±  0.002  ns/op
TrustFinalFields._static_final  avgt   15  1.901 ±  0.001  ns/op  # <--- !!!

…​and here the final field is folded, as can be seen in perfasm output:

3.04%   ↗  mov    %r10,(%rsp)
        │  mov    0x38(%rsp),%rsi
8.26%   │  mov    $0x1,%edx           ; <--- constant folded to 1
        │  ...
0.04%   │  test   %r11d,%r11d         ; check and run @Benchmark again
        ╰  je     BACK

Observations

Trusting instance final fields requires knowing the object we are operating with. But even then, we may pragmatically do it when we are sure it does not break applications — so, minimally, for known system classes. Constant folding through these final fields is the corner-stone for performance story for MethodHandle-s, VarHandle-s`, Atomic*FieldUpdaters and other high-performance implementations from the core library. Applications may try to use the experimental VM options, but the potential breakage from misbehaving applications may severely dampen the benefits.