Yansıma ile JIT optimizasyonlarını kırma


9

Yüksek derecede eşzamanlı singleton sınıfı için birim testleri ile uğraşırken, aşağıdaki garip davranışa rastladım (JDK 1.8.0_162'de test edildi):

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

Main () yönteminin son 2 satırı INSTANCE değerine katılmıyor - tahminim JIT, alan statik olduğu için yöntemden tamamen kurtuldu. Son anahtar kelimenin kaldırılması kod çıktısını doğru değerler yapar.

Singletons için sempatinizi (veya eksikliğinizi) bir kenara bırakarak ve böyle bir yansımayı kullanmanın sorun istediğini bir dakika unutmak - JIT optimizasyonlarının suçlanacağı yönündeki varsayım doğru mu? Öyleyse - yalnızca statik son alanlarla sınırlı mı?


1
Singleton, yalnızca bir örneğin var olabileceği bir sınıftır. Bu nedenle, bir singletonunuz yok, sadece bir static finalalana sahip bir sınıfınız var . Bunun yanı sıra, bu yansıma saldırısının JIT veya eşzamanlılık nedeniyle kırılıp kırılmayacağı önemli değildir.
Holger

@Holger bu kesmek sadece onu kullanan bir sınıfın çoklu test senaryoları için singleton alay etmeye çalışmak için birim testlerinde yapıldı. Eşzamanlılığın buna nasıl neden olabileceğini görmüyorum (yukarıdaki kodda hiç yok) ve gerçekten ne olduğunu bilmek istiyorum.
Kelm

1
Sorunuzda “yüksek eşzamanlı singleton sınıfı” dediniz ve ben onu neyin kırdığını “ önemli değil ” diyorum . Bu nedenle, belirli örnek kodunuz JIT nedeniyle kırılırsa ve bunun için bir çözüm bulursanız ve gerçek kod, JIT nedeniyle kırılmadan eşzamanlılık nedeniyle kırılmaya kadar değişirse, ne kazandınız?
Holger

@Holger tamam, ifadeler biraz fazla güçlüydü, bunun için üzgünüm. Demek istediğim şuydu - bir şeyin neden bu kadar korkunç bir şekilde yanlış gittiğini anlamıyorsak, gelecekte aynı şey tarafından ısırılmaya eğilimliyiz, bu yüzden "sadece gerçekleştiğini" varsaymak yerine sebebini bilmek istiyorum. Her neyse, cevap vermek için zaman ayırdığınız için teşekkürler!
Kelm

Yanıtlar:


7

Sorunuzu tam anlamıyla ele alırsak, “ … JIT optimizasyonlarının suçlanacağı yönündeki varsayım doğru mu? ”Yanıt evettir, JIT optimizasyonlarının bu özel örnekte bu davranıştan sorumlu olması muhtemeldir.

Ancak static finalalanları değiştirmek tamamen teknik özelliklerin dışında olduğundan, benzer şekilde kırabilecek başka şeyler de vardır. Örneğin, JMM'nin bu tür değişikliklerin bellek görünürlüğü için bir tanımı yoktur, bu nedenle, diğer iş parçacıklarının bu değişiklikleri fark edip etmediği veya ne zaman fark edileceği tamamen belirtilmez. Sürekli olarak fark etmeleri bile gerekmez, yani yeni değeri kullanabilirler, ardından senkronizasyon ilkellerinin varlığında bile eski değeri tekrar kullanabilirler.

Yine de, JMM ve optimize ediciyi burada ayırmak zordur.

Sorunuz “ … bunlar yalnızca statik son alanlarla mı sınırlı? ”Optimizasyonların elbette static finalalanlarla sınırlı olmadığı için cevaplanması çok daha zordur , ancak örneğin statik olmayan finalalanların davranışı aynı değildir ve teori ile pratik arasında da farklılıklar vardır.

Statik olmayan finalalanlar için, bazı durumlarda Yansıma yoluyla değişiklik yapılmasına izin verilir. Bu, dahili alanı değiştirmek için örneğe setAccessible(true)girmeden, bu tür bir modifikasyonu mümkün kılmak için yeterli olduğu gerçeğiyle belirtilir .Fieldmodifiers

Spesifikasyon diyor ki:

17,5.3. Sonraki finalAlan Değişiklikleri

Deserialization gibi bazı durumlarda, sistemin finalinşaattan sonra bir nesnenin alanlarını değiştirmesi gerekecektir . finalalanlar yansıma ve uygulamaya bağlı diğer araçlarla değiştirilebilir. Bunun makul semantiğe sahip olduğu tek desen, bir nesnenin oluşturulduğu ve daha sonra finalnesnenin alanlarının güncellendiği modeldir . Nesnenin finalalanlarındaki tüm güncelleştirmeler finaltamamlanıncaya kadar, nesnenin diğer iş parçacıklarına görünür kılınmaması veya alanların okunması gerekir . Bir finalalanın donması, hem finalalanın kurulduğu yapıcı sonunda hem de bir alanın her değişiklikten hemen sonra finalyansıma veya diğer özel mekanizmalarla gerçekleşir.

...

Başka bir sorun, spesifikasyonun finalalanların agresif optimizasyonuna izin vermesidir . Bir iş parçacığı içinde, bir finalalanın okumalarını final, yapıcıda yer almayan bir alanın bu değişiklikleriyle yeniden sıralamaya izin verilir .

Örnek 17.5.3-1. finalAlanların Agresif Optimizasyonu
class A {
    final int x;
    A() { 
        x = 1; 
    } 

    int f() { 
        return d(this,this); 
    } 

    int d(A a1, A a2) { 
        int i = a1.x; 
        g(a1); 
        int j = a2.x; 
        return j - i; 
    }

    static void g(A a) { 
        // uses reflection to change a.x to 2 
    } 
}

In dyöntemin, derleyici okur yeniden düzenlemek için izin verilir xve çağrı gserbestçe. Bu durumda, new A().f()geri dönebilirler -1, 0ya da 1.

Uygulamada, yukarıda açıklanan yasal senaryoları bozmadan agresif optimizasyonların mümkün olduğu doğru yerlerin belirlenmesi açık bir konudur , bu nedenle -XX:+TrustFinalNonStaticFields, belirtilmedikçe HotSpot JVM, statik olmayan finalalanları alanlarla aynı şekilde optimize etmez static final.

Tabii ki, alanı olarak bildirmediğinizde, finalJIT asla değişmeyeceğini varsayamaz, ancak iplik senkronizasyonu ilkellerinin yokluğunda, optimize ettiği kod yolunda gerçekleşen gerçek değişiklikleri ( yansıtıcı olanlar). Hala agresif erişimini optimize fakat sadece olabilir Yani olarak if- okur ve yazar hâlâ çalışıyor iplik içindeki programı sırayla olur. Böylece optimizasyonları sadece uygun senkronizasyon yapıları olmadan farklı bir evreden bakarken fark edersiniz.


birçok insan bu finals istismar etmeye çalışıyor gibi görünüyor , ancak, bazıları daha iyi performans kanıtlamış olsa da, tasarruf bazı nsdiğer kodları kırmaya değmez. Shenandoah'ın bayraklarından bazılarına geri
Eugene
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.