Ketika mengutak-atik unit test untuk kelas singleton yang sangat bersamaan, saya menemukan perilaku aneh berikut (diuji pada 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);
}
2 baris terakhir dari metode main () tidak setuju pada nilai INSTANCE - tebakan saya adalah bahwa JIT menyingkirkan metode sepenuhnya karena bidang final statis. Menghapus kata kunci terakhir membuat kode menghasilkan nilai yang benar.
Mengesampingkan simpati Anda (atau ketiadaannya) untuk lajang dan lupa sejenak bahwa menggunakan refleksi seperti ini meminta masalah - apakah asumsi saya benar bahwa optimisasi JIT yang harus disalahkan? Jika demikian - apakah hanya terbatas pada bidang akhir statis?
java
reflection
java-8
jit
Kelm
sumber
sumber
static final
bidang. Selain itu, tidak masalah apakah retasan refleksi ini rusak karena JIT atau konkurensi.Jawaban:
Mengambil pertanyaan Anda secara harfiah, “ ... apakah asumsi saya benar dalam hal optimasi JIT yang harus disalahkan? ”, Jawabannya adalah ya, sangat mungkin bahwa optimasi JIT bertanggung jawab atas perilaku ini dalam contoh khusus ini.
Tetapi karena mengubah
static final
bidang sama sekali tidak spesifik, ada hal lain yang dapat merusaknya dengan cara yang sama. Misalnya JMM tidak memiliki definisi untuk visibilitas memori dari perubahan tersebut, maka, itu sepenuhnya tidak ditentukan apakah atau ketika thread lain memperhatikan perubahan tersebut. Mereka bahkan tidak diharuskan untuk memperhatikannya secara konsisten, yaitu mereka dapat menggunakan nilai baru, diikuti dengan menggunakan nilai yang lama lagi, bahkan di hadapan primitif sinkronisasi.Padahal, JMM dan optimizer sulit untuk dipisahkan di sini.
Pertanyaan Anda “ ... apakah hanya terbatas pada bidang akhir statis? ”Jauh lebih sulit untuk dijawab, karena optimisasi tentu saja tidak terbatas pada
static final
bidang, tetapi perilaku, misalnyafinal
bidang non-statis , tidak sama dan memiliki perbedaan antara teori dan praktik juga.Untuk
final
bidang non-statis , modifikasi melalui Refleksi diperbolehkan dalam kondisi tertentu. Ini ditunjukkan oleh fakta bahwasetAccessible(true)
cukup untuk membuat modifikasi seperti itu mungkin, tanpa meretasField
contoh untuk mengubahmodifiers
bidang internal .Spesifikasi mengatakan:
Dalam praktiknya, menentukan tempat yang tepat di mana optimisasi agresif dimungkinkan tanpa melanggar skenario hukum yang dijelaskan di atas, merupakan masalah terbuka , jadi kecuali
-XX:+TrustFinalNonStaticFields
telah ditentukan, JVM HotSpot tidak akan mengoptimalkanfinal
bidang non-statis dengan cara yang sama sepertistatic final
bidang.Tentu saja, ketika Anda tidak mendeklarasikan isian sebagai
final
, JIT tidak dapat berasumsi bahwa itu tidak akan pernah berubah, meskipun, dengan tidak adanya primitif sinkronisasi ulir, ia dapat mempertimbangkan modifikasi aktual yang terjadi di jalur kode yang dioptimalkan (termasuk yang reflektif). Jadi itu mungkin masih mengoptimalkan akses secara agresif, tetapi hanya seolah-olah membaca dan menulis masih terjadi dalam urutan program dalam utas pelaksana. Jadi, Anda hanya akan melihat optimasi ketika melihatnya dari utas yang berbeda tanpa konstruksi sinkronisasi yang tepat.sumber
final
, tetapi, meskipun beberapa telah terbukti menjadi lebih baik, penghematan beberapans
tidak layak melanggar banyak kode lainnya. Alasan mengapa Shenandoah mundur pada beberapa benderanya misalnya