Есть ли предел для переопределения конечного статического поля с помощью Reflection? - PullRequest
0 голосов
/ 20 ноября 2018

В некоторых из моих юнит-тестов я столкнулся со странным поведением с отражением в конечном статическом поле.Ниже приведен пример, иллюстрирующий мою проблему.

У меня есть базовый класс Singleton, который содержит целое число

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;
    }

}

Мой тестовый цикл зацикливается и задает через Reflection значение 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);
        }
    }

}

Результат довольно удивителен, потому что он не постоянен, мой тест не проходит около итерации ~ 1000, но кажется, что он никогда не будет одинаковым.

Кто-нибудь уже сталкивался с этой проблемой?

Ответы [ 2 ]

0 голосов
/ 20 ноября 2018

Это из-за оптимизации JIT.Чтобы доказать это, отключите его, используя следующую опцию VM:

-Djava.compiler=NONE

В этом случае все итерации 10_000 будут работать.

Или исключите метод BasicHolder.getVALUE изскомпилировано:

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

На самом деле под капотом происходит то, что после итерации nth горячий метод getVALUE компилируется, а static final Integer VALUE агрессивно оптимизируется (это действительно просто)постоянная времени 1 ).С этого момента утверждение начинает проваливаться.

Вывод -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: Постоянные времени .

0 голосов
/ 20 ноября 2018

В JLS упоминается, что изменение конечных полей после построения проблематично - см. 17.5.окончательная семантика поля

Поля, объявленные окончательными, инициализируются один раз, но никогда не меняются при нормальных обстоятельствах.Подробная семантика конечных полей несколько отличается от семантики нормальных полей.В частности, компиляторы имеют большую свободу перемещать чтения конечных полей через барьеры синхронизации и вызовы произвольных или неизвестных методов.Соответственно, компиляторам разрешается сохранять значение конечного поля, кэшированного в регистре, и не перезагружать его из памяти в ситуациях, когда необходимо перезагружать неконечное поле.

и 17.5.3.Последующая модификация конечных полей:

Другая проблема заключается в том, что спецификация допускает агрессивную оптимизацию конечных полей.Внутри потока допустимо переупорядочивать операции чтения конечного поля с теми модификациями конечного поля, которые не имеют места в конструкторе.

В дополнение к этому JavaDocs Field.set также включает предупреждение об этом:

Установка окончательного поля таким способом:имеет смысл только во время десериализации или реконструкции экземпляров классов с пустыми конечными полями, прежде чем они станут доступны для доступа другими частями программы.Использование в любом другом контексте может привести к непредсказуемым последствиям, включая случаи, когда другие части программы продолжают использовать исходное значение этого поля.

Кажется, что здесь мы наблюдаем, что JIT использует возможности переупорядочения и кэширования, предоставляемые Спецификацией языка.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...