Podczas majstrowania przy testach jednostkowych dla wysoce współbieżnej klasy singleton natknąłem się na następujące dziwne zachowanie (testowane na 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);
}
Ostatnie 2 wiersze metody main () nie zgadzają się co do wartości INSTANCE - domyślam się, że JIT całkowicie pozbył się tej metody, ponieważ pole jest statyczne. Usunięcie końcowego słowa kluczowego powoduje, że kod wyjściowy ma prawidłowe wartości.
Odkładając na bok swoją sympatię (lub jej brak) na singletony i na chwilę zapominając, że użycie tego rodzaju refleksji prosi o kłopoty - czy moje założenie jest słuszne, jeśli chodzi o to, że optymalizacja JIT jest winna? Jeśli tak - czy są one ograniczone tylko do statycznych pól końcowych?
java
reflection
java-8
jit
Kelm
źródło
źródło
static final
polem. Poza tym nie ma znaczenia, czy hack odbicia zrywa się z powodu JIT czy współbieżności.Odpowiedzi:
Biorąc twoje pytanie dosłownie: „ … czy moje założenie jest słuszne, że winą są optymalizacje JIT? ”, Odpowiedź brzmi tak, jest bardzo prawdopodobne, że optymalizacje JIT są odpowiedzialne za to zachowanie w tym konkretnym przykładzie.
Ale ponieważ zmiana
static final
pól jest całkowicie niezgodna ze specyfikacją, istnieją inne rzeczy, które mogą ją złamać podobnie. Np. JMM nie ma definicji widoczności pamięci takich zmian, dlatego nie jest określone, czy lub kiedy inne wątki zauważą takie zmiany. Nie są nawet zobowiązani do ciągłego zauważania tego, tzn. Mogą użyć nowej wartości, a następnie ponownie użyć starej wartości, nawet w obecności operacji podstawowych synchronizacji.Chociaż JMM i optymalizator i tak są tutaj trudne do oddzielenia.
Twoje pytanie „ … czy są one ograniczone tylko do statycznych pól końcowych? ”Jest o wiele trudniej odpowiedzieć, ponieważ optymalizacje nie są oczywiście ograniczone do
static final
pól, ale zachowanie, np.final
Pól niestatycznych , nie jest takie samo i ma również różnice między teorią a praktyką.W przypadku
final
pól niestatycznych modyfikacje przez odbicie są dozwolone w pewnych okolicznościach. Wskazuje na to fakt, żesetAccessible(true)
wystarczy, aby taka modyfikacja była możliwa, bez włamywania się doField
instancji w celu zmianymodifiers
pola wewnętrznego .Specyfikacja mówi:
W praktyce określenie właściwych miejsc, w których możliwe są agresywne optymalizacje bez naruszenia opisanych powyżej scenariuszy prawnych, jest kwestią otwartą , więc jeśli
-XX:+TrustFinalNonStaticFields
nie zostanie to określone, HotSpot JVM nie będzie optymalizowałfinal
pól niestatycznych w taki sam sposób, jakstatic final
pól.Oczywiście, gdy nie zadeklarujesz pola jako
final
, JIT nie może zakładać, że nigdy się nie zmieni, jednak przy braku operacji podstawowych synchronizacji wątków może rozważyć faktyczne modyfikacje zachodzące w zoptymalizowanej ścieżce kodu (w tym odblaskowe). Może więc nadal agresywnie optymalizować dostęp, ale tylko tak, jakby odczyty i zapisy wciąż zachodziły w kolejności programów w wykonywanym wątku. Optymalizacje można zauważyć tylko wtedy, gdy patrzy się na to z innego wątku bez odpowiednich konstrukcji synchronizacyjnych.źródło
final
, ale chociaż niektóre okazały się bardziej wydajne, oszczędnościns
nie są warte zepsucia wielu innych kodów. Powód, dla którego Shenandoah wycofuje się na przykład na niektórych swoich flagach