Skip to content

📈 Benchmark 003: Getting Field Values

📘 Preface

Reading from a field may appear costless, but the performance varies depending on how the field is accessed. Whether the field is accessed directly, via reflection, or through VarHandles (MethodHandles pre Java 9!), the JVM handles these operations differently.

🔬 Test Case

java
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@Fork(1)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
public class BM_003_GetField {

  public static class TestClass {

    static final TestClass INSTANCE = new TestClass();

    static final Field STATIC_FIELD, FIELD;
    static final VarHandle STATIC_FIELD_HANDLE, FIELD_HANDLE;

    static {
      try {
        STATIC_FIELD = TestClass.class.getField("staticField");
        STATIC_FIELD_HANDLE = MethodHandles.lookup().unreflectVarHandle(STATIC_FIELD);
        FIELD = TestClass.class.getField("field");
        FIELD_HANDLE = MethodHandles.lookup().unreflectVarHandle(FIELD);
      } catch (ReflectiveOperationException e) {
        throw new RuntimeException(e);
      }
    }

    public static int staticField = 42;
    public int field = 42;
  }

  @Benchmark
  @OperationsPerInvocation(1000)
  public void staticField(Blackhole bh) {
    bh.consume(TestClass.staticField);
  }

  @Benchmark
  @OperationsPerInvocation(1000)
  public void staticFieldReflection(Blackhole bh) throws IllegalAccessException {
    bh.consume((int) TestClass.STATIC_FIELD.get(null));
  }

  @Benchmark
  @OperationsPerInvocation(1000)
  public void staticFieldVH(Blackhole bh) {
    bh.consume((int) TestClass.STATIC_FIELD_HANDLE.get());
  }

  @Benchmark
  @OperationsPerInvocation(1000)
  public void field(Blackhole bh) {
    bh.consume(TestClass.INSTANCE.field);
  }

  @Benchmark
  @OperationsPerInvocation(1000)
  public void fieldReflection(Blackhole bh) throws IllegalAccessException {
    bh.consume((int) TestClass.FIELD.get(TestClass.INSTANCE));
  }

  @Benchmark
  @OperationsPerInvocation(1000)
  public void fieldVH(Blackhole bh) {
    bh.consume((int) TestClass.FIELD_HANDLE.get(TestClass.INSTANCE));
  }
}

✅ Results

Benchmark                              Mode  Cnt   Score    Error  Units
BM_003_GetField.field                  avgt    5   0.001 ±  0.001  ns/op
BM_003_GetField.fieldReflection        avgt    5   0.003 ±  0.001  ns/op
BM_003_GetField.fieldVH                avgt    5  ≈ 10⁻³           ns/op
BM_003_GetField.staticField            avgt    5  ≈ 10⁻³           ns/op
BM_003_GetField.staticFieldReflection  avgt    5   0.002 ±  0.001  ns/op
BM_003_GetField.staticFieldVH          avgt    5   0.001 ±  0.001  ns/op

🔎 Findings

We can see that VarHandles, when stored in a static final variable can be inlined to basically the same as a field access. Reflection still lags behind a little.