14

I have been faced in some of my Unit test with a strange behaviour with Reflection on final static field. Below is an example illustrating my issue.

I have a basic Singleton class that holds an Integer

public class BasicHolder {
    private static BasicHolder instance = new BasicHolder();

    public static BasicHolder getInstance() {
        return instance;
    }

    private BasicHolder() {
    }

    private final static Integer VALUE = new Integer(0);

    public Integer getVALUE() {
        return VALUE;
    }

}

My test case is looping and setting through Reflection the VALUE to the iteration index and then asserting that the VALUE is rightfully equal to the iteration index.

class TestStaticLimits {
    private static final Integer NB_ITERATION = 10_000;

    @Test
    void testStaticLimit() {

        for (Integer i = 0; i < NB_ITERATION; i++) {
            setStaticFieldValue(BasicHolder.class, "VALUE", i);
            Assertions.assertEquals(i, BasicHolder.getInstance().getVALUE(), "REFLECTION DID NOT WORK for iteration "+i);
            System.out.println("iter " + i + " ok" );

        }
    }

    private static void setStaticFieldValue(final Class obj, final String fieldName, final Object fieldValue) {
        try {
            final Field field = obj.getDeclaredField(fieldName);
            field.setAccessible(true);
            final Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            field.set(null, fieldValue);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException("Error while setting field [" + fieldName + "] on object " + obj + " Message " + e.getMessage(), e);
        }
    }

}

The result is quite surprising because it's not constant, my test fails around iteration ~1000 but it never seems to be always the same.

Anyone has already faced this issue ?

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
Samy Elaiassi
  • 516
  • 3
  • 12
  • 1
    I have reopened this question, as it is **not** a duplicate of https://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection. OP clearly understands how to modify a `private static final` field. – Jacob G. Nov 20 '18 at 15:15
  • 3
    I have managed to repro this https://ideone.com/bZ7nmR – Andy Turner Nov 20 '18 at 15:20
  • 10
    It could be that after ~1000 iterations the JIT kicks in and compiles the loop into native code. And since the field "is expected to be final" the JIT does inline the current value of the field – Thomas Kläger Nov 20 '18 at 15:22
  • 1
    @ThomasKläger this sounds plausible: with `-XX:+PrintCompilation`, I get a flurry of stuff printed right before it fails. – Andy Turner Nov 20 '18 at 15:24
  • 6
    @ThomasKläger is correct. The program succeeds when run with `-Djava.compiler=NONE`. Write an answer and claim your points! – Jacob G. Nov 20 '18 at 15:26
  • 3
    @Hulk `private final static Integer VALUE = new Integer(0);` is not a constant variable. – Sotirios Delimanolis Nov 20 '18 at 15:34
  • @SotiriosDelimanolis ok, true. Current wording in the JLS is slightly different [17.5.3. Subsequent Modification of final Fields](https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.5.3), but still, an `Integer` cannot be a [Constant Expression](https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.28) – Hulk Nov 20 '18 at 15:38

2 Answers2

10

The JLS mentions that modifying final fields after construction is problematic - see 17.5. final Field Semantics

Fields declared final are initialized once, but never changed under normal circumstances. The detailed semantics of final fields are somewhat different from those of normal fields. In particular, compilers have a great deal of freedom to move reads of final fields across synchronization barriers and calls to arbitrary or unknown methods. Correspondingly, compilers are allowed to keep the value of a final field cached in a register and not reload it from memory in situations where a non-final field would have to be reloaded.

and 17.5.3. Subsequent Modification of final Fields:

Another problem is that the specification allows aggressive optimization of final fields. Within a thread, it is permissible to reorder reads of a final field with those modifications of a final field that do not take place in the constructor.

In addition to that, the JavaDocs of Field.set also include a warning about this:

Setting a final field in this way is meaningful only during deserialization or reconstruction of instances of classes with blank final fields, before they are made available for access by other parts of a program. Use in any other context may have unpredictable effects, including cases in which other parts of a program continue to use the original value of this field.

It seems that what we are witnessing here is the JIT taking advantage of the reordering and caching possibilities granted by the Language Specification.

Hulk
  • 6,399
  • 1
  • 30
  • 52
4

It's because of the JIT optimization. To prove this, disable it using the following VM option:

-Djava.compiler=NONE

In this case all 10_000 iterations will work.

Or, exclude the BasicHolder.getVALUE method from being compiled:

-XX:CompileCommand=exclude,src/main/BasicHolder.getVALUE

What actually happens under the hood is that after nth iteration, the hot method getVALUE is being compiled and static final Integer VALUE is being aggressively optimized (this is really the just-in-time constant1). From this point, the assertion starts to fail.

The output of the -XX:+PrintCompilation with my comments:

val 1       # System.out.println("val " + BasicHolder.getInstance().getVALUE());
val 2
val 3
...
922  315    3    src.main.BasicHolder::getInstance (4 bytes)   # Method compiled
922  316    3    src.main.BasicHolder::getVALUE    (4 bytes)   # Method compiled
...
val 1563    # after compilation
val 1563
val 1563
val 1563
...

1 - JVM Anatomy Park: Just-In-Time Constants.

Oleksandr Pyrohov
  • 14,685
  • 6
  • 61
  • 90
  • @SotiriosDelimanolis "Correspondingly, compilers are allowed to keep the value of a final field cached in a register and not reload it from memory in situations where a non-final field would have to be reloaded." from the other answer, i think the JIL try to optimize after a bunch of access to the same final reference – Samy Elaiassi Nov 20 '18 at 16:50