Anehnya, kode berikut ini menghasilkan:
/
-1
Kode:
public class LoopOutPut {
public static void main(String[] args) {
LoopOutPut loopOutPut = new LoopOutPut();
for (int i = 0; i < 30000; i++) {
loopOutPut.test();
}
}
public void test() {
int i = 8;
while ((i -= 3) > 0) ;
String value = i + "";
if (!value.equals("-1")) {
System.out.println(value);
System.out.println(i);
}
}
}
Saya mencoba berkali-kali untuk menentukan berapa kali ini akan terjadi, tetapi, sayangnya, pada akhirnya tidak pasti, dan saya menemukan bahwa output -2 terkadang berubah menjadi suatu periode. Selain itu, saya juga mencoba menghapus while loop dan output -1 tanpa masalah. Siapa yang bisa memberi tahu saya alasannya?
Informasi versi JDK:
HopSpot 64-Bit 1.8.0.171
IDEA 2019.1.1
Jawaban:
Ini dapat direproduksi secara andal (atau tidak direproduksi, tergantung pada apa yang Anda inginkan) dengan
openjdk version "1.8.0_222"
(digunakan dalam analisis saya), OpenJDK12.0.1
(menurut Oleksandr Pyrohov) dan OpenJDK 13 (menurut Carlos Heuberger).Saya menjalankan kode dengan
-XX:+PrintCompilation
waktu yang cukup untuk mendapatkan perilaku dan inilah perbedaannya.Implementasi Buggy (menampilkan output):
Proses yang benar (tidak ada tampilan):
Kita dapat melihat satu perbedaan signifikan. Dengan eksekusi yang benar, kami mengkompilasi
test()
dua kali. Sekali di awal, dan sekali lagi sesudahnya (mungkin karena JIT memperhatikan betapa panas metode ini). Dalam eksekusi buggytest()
dikompilasi (atau didekompilasi) 5 kali.Selain itu, berjalan dengan
-XX:-TieredCompilation
(yang mengartikan, atau menggunakanC2
) atau dengan-Xbatch
(yang memaksa kompilasi untuk berjalan di utas utama, bukannya paralel), output dijamin dan dengan 30000 iterasi mencetak banyak hal, sehinggaC2
kompiler tampaknya untuk menjadi pelakunya. Ini dikonfirmasi dengan berjalan dengan-XX:TieredStopAtLevel=1
, yang menonaktifkanC2
dan tidak menghasilkan output (berhenti di level 4 menunjukkan bug lagi).Dalam eksekusi yang benar, metode ini pertama kali dikompilasi dengan kompilasi Level 3 , kemudian setelah itu dengan Level 4.
Dalam eksekusi buggy, kompilasi sebelumnya diabaikan (
made non entrant
) dan dikompilasi lagi pada Level 3 (yaituC1
, lihat tautan sebelumnya).Jadi itu pasti bug
C2
, walaupun saya tidak benar-benar yakin apakah fakta bahwa itu akan kembali ke kompilasi Level 3 memengaruhinya (dan mengapa itu kembali ke level 3, masih banyak ketidakpastian masih).Anda dapat membuat kode rakitan dengan baris berikut untuk masuk lebih dalam ke lubang kelinci (lihat juga ini untuk mengaktifkan pencetakan rakitan).
Pada titik ini saya mulai kehabisan keterampilan, perilaku buggy mulai menunjukkan ketika versi kompilasi sebelumnya dibuang, tetapi betapa sedikit keterampilan perakitan yang saya miliki dari tahun 90-an, jadi saya akan membiarkan seseorang yang lebih pintar daripada saya mengambilnya dari sini.
Kemungkinan sudah ada laporan bug tentang ini, karena kode tersebut disampaikan kepada OP oleh orang lain, dan karena semua kode C2 bukan tanpa bug . Saya berharap analisis ini bermanfaat bagi orang lain seperti juga bagi saya.
Seperti yang ditunjukkan oleh Yang Mulia Apangin dalam komentar, ini adalah bug baru - baru ini . Banyak yang diwajibkan untuk semua orang yang tertarik dan membantu :)
sumber
C2
- telah melihat kode assembler yang dihasilkan (dan mencoba memahaminya) menggunakan JitWatch -C1
kode yang dihasilkan masih menyerupai bytecode,C2
sama sekali berbeda (saya bahkan tidak bisa menemukan inisialisasii
dengan 8)Ini sejujurnya cukup aneh, karena kode itu secara teknis seharusnya tidak pernah keluar karena ...
... harus selalu menghasilkan
i
menjadi-1
(8 - 3 = 5; 5 - 3 = 2; 2 - 3 = -1). Yang lebih aneh lagi, ia tidak pernah menghasilkan mode debug pada IDE saya.Menariknya, saat saya menambahkan cek sebelum konversi ke
String
, maka tidak ada masalah ...Hanya dua poin praktik pengkodean yang baik ...
String.valueOf()
.equals()
, bukan argumen, sehingga meminimalkan NullPointerExceptions.Satu-satunya cara saya mendapatkan ini tidak terjadi adalah dengan menggunakan
String.format()
... pada dasarnya sepertinya Jawa membutuhkan sedikit waktu untuk menghirup napas :)
EDIT: Ini mungkin sepenuhnya kebetulan, tetapi tampaknya ada beberapa korespondensi antara nilai yang dicetak dan Tabel ASCII .
i
=-1
, karakter yang ditampilkan adalah/
(nilai desimal ASCII 47)i
=-2
, karakter yang ditampilkan adalah.
(nilai desimal ASCII 46)i
=-3
, karakter yang ditampilkan adalah-
(nilai desimal ASCII 45)i
=-4
, karakter yang ditampilkan adalah,
(nilai desimal ASCII 44)i
=-5
, karakter yang ditampilkan adalah+
(nilai desimal ASCII 43)i
=-6
, karakter yang ditampilkan adalah*
(nilai desimal ASCII 42)i
=-7
, karakter yang ditampilkan adalah)
(nilai desimal ASCII 41)i
=-8
, karakter yang ditampilkan adalah(
(nilai desimal ASCII 40)i
=-9
, karakter yang ditampilkan adalah'
(nilai desimal ASCII 39)Apa yang benar-benar menarik adalah bahwa karakter pada ASCII desimal 48 adalah nilai
0
dan 48 - 1 = 47 (karakter/
), dll ...sumber
(int)'/' == 47
;(char)-1
tidak ditentukan0xFFFF
adalah <bukan karakter> di Unicode)getNumericValue()
hubungannya dengan kode yang diberikan ??? dan bagaimana cara dikonversi-1
ke'/'
??? Kenapa tidak'-'
,getNumericValue('-')
juga-1
??? (BTW banyak metode kembali-1
)getNumericValue()
padavalue
(/
) untuk mendapatkan nilai karakter. Anda 100% benar bahwa nilai desimal ASCII/
harus 47 (itu yang saya juga harapkan), tetapigetNumericValue()
mengembalikan -1 pada titik itu seperti yang saya tambahkanSystem.out.println(Character.getNumericValue(value.toCharArray()[0]));
. Saya dapat melihat kebingungan yang Anda maksudkan dan telah memperbarui posnya.Tidak tahu mengapa Java memberikan output acak seperti itu tetapi masalahnya ada pada rangkaian Anda yang gagal untuk nilai yang lebih besar
i
di dalamfor
loop.Jika Anda mengganti
String value = i + "";
baris denganString value = String.valueOf(i) ;
kode Anda berfungsi seperti yang diharapkan.Penggabungan menggunakan
+
untuk mengkonversi int ke string adalah asli dan mungkin buggy (Anehnya kita menemukan sekarang mungkin) dan menyebabkan masalah seperti itu.Catatan: Saya mengurangi nilai i inside untuk loop ke 10000 dan saya tidak menghadapi masalah dengan
+
penggabungan.Masalah ini harus dilaporkan kepada pemangku kepentingan Jawa & mereka dapat memberikan pendapat yang sama.
Sunting Saya memperbarui nilai i in untuk loop ke 3 juta dan melihat serangkaian kesalahan baru seperti di bawah ini:
Versi Java saya adalah 8.
sumber
StringConcatFactory
(OpenJDK 13) atauStringBuilder
(Java 8)StringConcatFactory
kelas. tapi sejauh yang saya tahu java sampai java 8 java don; t mendukung operator overloadingException in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
kesalahan. Aneh.i + ""
dikompilasi persis sepertinew StringBuilder().append(i).append("").toString()
di Java 8, dan menggunakannya yang akhirnya juga menghasilkan output