简体   繁体   English

使用Reflection覆盖最终静态字段是否有限制?

[英]Is there a limit to overriding final static field with Reflection?

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 我有一个基本的Singleton类,它包含一个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. 我的测试用例是循环并设置Reflection the VALUE到迭代索引,然后声明VALUE正确地等于迭代索引。

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. 结果是非常令人惊讶的,因为它不是常数,我的测试在迭代〜1000时失败但它似乎永远不会一样。

Anyone has already faced this issue ? 任何人都已经遇到过这个问题?

The JLS mentions that modifying final fields after construction is problematic - see 17.5. JLS提到在施工后修改最终油田是有问题的 - 见17.5。 final Field Semantics 最终的场语义学

Fields declared final are initialized once, but never changed under normal circumstances. 声明为final的字段初始化一次,但在正常情况下从未更改过。 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. 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: 除此之外,Field.setJavaDocs还包含一个警告:

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. 我们在这里看到的似乎是JIT利用语言规范授予的重新排序和缓存可能性。

It's because of the JIT optimization. 这是因为JIT优化。 To prove this, disable it using the following VM option: 要证明这一点,请使用以下VM选项禁用它:

-Djava.compiler=NONE

In this case all 10_000 iterations will work. 在这种情况下,所有10_000次迭代都将起作用。

Or, exclude the BasicHolder.getVALUE method from being compiled: 或者,从编译中排除BasicHolder.getVALUE方法:

-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 constant 1 ). 实际发生的事情是,在nth次迭代之后,正在编译热方法getVALUE并且正在积极地优化static final Integer VALUE (这实际上是即时常数1 )。 From this point, the assertion starts to fail. 从这一点来看,断言开始失败。

The output of the -XX:+PrintCompilation with my comments: -XX:+PrintCompilation的输出带有我的注释:

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 . 1 - JVM解剖公园:准时制常数

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM