Beim Herumspielen mit Komponententests für eine hochkonkurrierende Singleton-Klasse bin ich auf das folgende seltsame Verhalten gestoßen (getestet mit JDK 1.8.0_162):
private static class SingletonClass {
static final SingletonClass INSTANCE = new SingletonClass(0);
final int value;
static SingletonClass getInstance() {
return INSTANCE;
}
SingletonClass(int value) {
this.value = value;
}
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
System.out.println(SingletonClass.getInstance().value); // 0
// Change the instance to a new one with value 1
setSingletonInstance(new SingletonClass(1));
System.out.println(SingletonClass.getInstance().value); // 1
// Call getInstance() enough times to trigger JIT optimizations
for(int i=0;i<100_000;++i){
SingletonClass.getInstance();
}
System.out.println(SingletonClass.getInstance().value); // 1
setSingletonInstance(new SingletonClass(2));
System.out.println(SingletonClass.INSTANCE.value); // 2
System.out.println(SingletonClass.getInstance().value); // 1 (2 expected)
}
private static void setSingletonInstance(SingletonClass newInstance) throws NoSuchFieldException, IllegalAccessException {
// Get the INSTANCE field and make it accessible
Field field = SingletonClass.class.getDeclaredField("INSTANCE");
field.setAccessible(true);
// Remove the final modifier
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
// Set new value
field.set(null, newInstance);
}
Die letzten beiden Zeilen der main () -Methode stimmen nicht mit dem Wert von INSTANCE überein. Ich vermute, dass JIT die Methode vollständig entfernt hat, da das Feld statisch final ist. Durch Entfernen des letzten Schlüsselworts gibt der Code korrekte Werte aus.
Wenn Sie Ihr Mitgefühl (oder das Fehlen davon) für Singletons beiseite lassen und für eine Minute vergessen, dass die Verwendung einer solchen Reflexion Ärger bereitet - ist meine Annahme richtig, dass JIT-Optimierungen schuld sind? Wenn ja - sind diese nur auf statische Endfelder beschränkt?
java
reflection
java-8
jit
Kelm
quelle
quelle
static final
Feld. Außerdem spielt es keine Rolle, ob dieser Reflection-Hack aufgrund von JIT oder Parallelität unterbrochen wird.Antworten:
Nehmen Sie Ihre Frage wörtlich: „ … ist meine Annahme richtig, dass JIT-Optimierungen schuld sind? ”, Die Antwort lautet ja, es ist sehr wahrscheinlich, dass die JIT-Optimierungen in diesem speziellen Beispiel für dieses Verhalten verantwortlich sind.
Da das Ändern von
static final
Feldern jedoch völlig außerhalb der Spezifikation liegt, gibt es andere Dinge, die es ähnlich brechen können. Zum Beispiel hat das JMM keine Definition für die Speichersichtbarkeit solcher Änderungen, daher ist es völlig unbestimmt, ob oder wann andere Threads solche Änderungen bemerken. Sie müssen es nicht einmal konsistent bemerken, dh sie können den neuen Wert verwenden, gefolgt von der erneuten Verwendung des alten Werts, selbst wenn Synchronisationsprimitive vorhanden sind.Allerdings sind JMM und Optimierer hier ohnehin schwer zu trennen.
Ihre Frage „ … sind diese nur auf statische Endfelder beschränkt? ”Ist viel schwieriger zu beantworten, da Optimierungen natürlich nicht auf
static final
Felder beschränkt sind, aber das Verhalten von z. B. nicht statischenfinal
Feldern nicht dasselbe ist und auch Unterschiede zwischen Theorie und Praxis aufweist.Bei nicht statischen
final
Feldern sind unter bestimmten Umständen Änderungen über Reflection zulässig. Dies wird durch die Tatsache angezeigt, dass diessetAccessible(true)
ausreicht, um eine solche Änderung zu ermöglichen, ohne sich in dieField
Instanz zu hacken , um das internemodifiers
Feld zu ändern .Die Spezifikation sagt:
In der Praxis ist die Ermittlung der richtigen Stellen, an denen aggressive Optimierungen möglich sind, ohne die oben beschriebenen rechtlichen Szenarien zu verletzen, ein offenes Problem . Sofern
-XX:+TrustFinalNonStaticFields
nicht anders angegeben, optimiert die HotSpot-JVM nicht statischefinal
Felder nicht wiestatic final
Felder.Wenn Sie das Feld nicht als deklarieren
final
, kann die JIT natürlich nicht davon ausgehen, dass es sich niemals ändert. Wenn jedoch keine Grundelemente für die Thread-Synchronisierung vorhanden sind, werden möglicherweise die tatsächlichen Änderungen im optimierten Codepfad (einschließlich des) berücksichtigt reflektierende). Daher kann es den Zugriff immer noch aggressiv optimieren, aber nur so, als ob die Lese- und Schreibvorgänge immer noch in der Programmreihenfolge innerhalb des ausführenden Threads erfolgen. Sie würden die Optimierungen also nur bemerken, wenn Sie sie von einem anderen Thread aus ohne geeignete Synchronisationskonstrukte betrachten.quelle
final
s auszunutzen , aber obwohl sich einige als leistungsfähiger erwiesen haben,ns
lohnt es sich nicht, viele andere Codes zu beschädigen. Grund, warum Shenandoah zum Beispiel auf einigen seiner Flaggen zurückweicht