![](/img/trans.png)
[英]Set private field with reflection works on static OR final, but not static final (combined)
[英]Is there a limit to overriding final static field with Reflection?
我在一些單元測試中遇到了一個奇怪的行為反射最終靜態場。 以下是說明我的問題的示例。
我有一個基本的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;
}
}
我的測試用例是循環並設置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);
}
}
}
結果是非常令人驚訝的,因為它不是常數,我的測試在迭代〜1000時失敗但它似乎永遠不會一樣。
任何人都已經遇到過這個問題?
JLS提到在施工后修改最終油田是有問題的 - 見17.5。 最終的場語義學
聲明為final的字段初始化一次,但在正常情況下從未更改過。 最終字段的詳細語義與普通字段的語義略有不同。 特別是,編譯器有很大的自由來跨越同步障礙移動最終字段的讀取,並調用任意或未知的方法。 相應地,允許編譯器將最終字段的值保存在寄存器中,而不是在必須重新加載非最終字段的情況下從內存重新加載它。
另一個問題是規范允許對最終字段進行積極優化。 在一個線程中,允許使用構造函數中不發生的最終字段的那些修改來重新排序最終字段的讀取。
除此之外,Field.set的JavaDocs還包含一個警告:
以這種方式設置最終字段僅在反序列化或重建具有空白最終字段的類的實例期間才有意義,然后才能使程序的其他部分訪問它們。 在任何其他上下文中使用可能具有不可預測的影響,包括程序的其他部分繼續使用該字段的原始值的情況。
我們在這里看到的似乎是JIT利用語言規范授予的重新排序和緩存可能性。
這是因為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解剖公園:准時制常數 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.