Questo può essere riprodotto in modo affidabile (o non riprodotto, a seconda di ciò che si desidera) con openjdk version "1.8.0_222"
(utilizzato nella mia analisi), OpenJDK 12.0.1
(secondo Oleksandr Pyrohov) e OpenJDK 13 (secondo Carlos Heuberger).
Ho eseguito il codice con -XX:+PrintCompilation
tempi sufficienti per ottenere entrambi i comportamenti e qui ci sono le differenze.
Implementazione con errori (visualizza l'output):
--- Previous lines are identical in both
54 17 3 java.lang.AbstractStringBuilder::<init> (12 bytes)
54 23 3 LoopOutPut::test (57 bytes)
54 18 3 java.lang.String::<init> (82 bytes)
55 21 3 java.lang.AbstractStringBuilder::append (62 bytes)
55 26 4 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)
55 20 3 java.lang.StringBuilder::<init> (7 bytes)
56 19 3 java.lang.StringBuilder::toString (17 bytes)
56 25 3 java.lang.Integer::getChars (131 bytes)
56 22 3 java.lang.StringBuilder::append (8 bytes)
56 27 4 java.lang.String::equals (81 bytes)
56 10 3 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes) made not entrant
56 28 4 java.lang.AbstractStringBuilder::append (50 bytes)
56 29 4 java.lang.String::getChars (62 bytes)
56 24 3 java.lang.Integer::stringSize (21 bytes)
58 14 3 java.lang.String::getChars (62 bytes) made not entrant
58 33 4 LoopOutPut::test (57 bytes)
59 13 3 java.lang.AbstractStringBuilder::append (50 bytes) made not entrant
59 34 4 java.lang.Integer::getChars (131 bytes)
60 3 3 java.lang.String::equals (81 bytes) made not entrant
60 30 4 java.util.Arrays::copyOfRange (63 bytes)
61 25 3 java.lang.Integer::getChars (131 bytes) made not entrant
61 32 4 java.lang.String::<init> (82 bytes)
61 16 3 java.util.Arrays::copyOfRange (63 bytes) made not entrant
61 31 4 java.lang.AbstractStringBuilder::append (62 bytes)
61 23 3 LoopOutPut::test (57 bytes) made not entrant
61 33 4 LoopOutPut::test (57 bytes) made not entrant
62 35 3 LoopOutPut::test (57 bytes)
63 36 4 java.lang.StringBuilder::append (8 bytes)
63 18 3 java.lang.String::<init> (82 bytes) made not entrant
63 38 4 java.lang.StringBuilder::append (8 bytes)
64 21 3 java.lang.AbstractStringBuilder::append (62 bytes) made not entrant
Esecuzione corretta (nessuna visualizzazione):
--- Previous lines identical in both
55 23 3 LoopOutPut::test (57 bytes)
55 17 3 java.lang.AbstractStringBuilder::<init> (12 bytes)
56 18 3 java.lang.String::<init> (82 bytes)
56 20 3 java.lang.StringBuilder::<init> (7 bytes)
56 21 3 java.lang.AbstractStringBuilder::append (62 bytes)
56 26 4 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)
56 19 3 java.lang.StringBuilder::toString (17 bytes)
57 22 3 java.lang.StringBuilder::append (8 bytes)
57 24 3 java.lang.Integer::stringSize (21 bytes)
57 25 3 java.lang.Integer::getChars (131 bytes)
57 27 4 java.lang.String::equals (81 bytes)
57 28 4 java.lang.AbstractStringBuilder::append (50 bytes)
57 10 3 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes) made not entrant
57 29 4 java.util.Arrays::copyOfRange (63 bytes)
60 16 3 java.util.Arrays::copyOfRange (63 bytes) made not entrant
60 13 3 java.lang.AbstractStringBuilder::append (50 bytes) made not entrant
60 33 4 LoopOutPut::test (57 bytes)
60 34 4 java.lang.Integer::getChars (131 bytes)
61 3 3 java.lang.String::equals (81 bytes) made not entrant
61 32 4 java.lang.String::<init> (82 bytes)
62 25 3 java.lang.Integer::getChars (131 bytes) made not entrant
62 30 4 java.lang.AbstractStringBuilder::append (62 bytes)
63 18 3 java.lang.String::<init> (82 bytes) made not entrant
63 31 4 java.lang.String::getChars (62 bytes)
Possiamo notare una differenza significativa. Con l'esecuzione corretta compiliamo test()
due volte. Una volta all'inizio, e ancora una volta in seguito (presumibilmente perché la JIT nota quanto sia caldo il metodo). Nell'esecuzione con buggy test()
viene compilata (o decompilata) 5 volte.
Inoltre, in esecuzione con -XX:-TieredCompilation
(che interpreta o utilizza C2
) o con -Xbatch
(che forza l'esecuzione della compilazione nel thread principale, anziché in parallelo), l'output è garantito e con 30000 iterazioni stampa un sacco di cose, quindi il C2
compilatore sembra essere il colpevole. Ciò è confermato eseguendo con -XX:TieredStopAtLevel=1
, che disabilita C2
e non produce output (l'arresto al livello 4 mostra nuovamente il bug).
Nell'esecuzione corretta, il metodo viene prima compilato con la compilazione di livello 3 , quindi successivamente con il livello 4.
Nell'esecuzione con buggy, le compilazioni precedenti vengono scartate ( made non entrant
) ed è di nuovo compilata al livello 3 (che è C1
, vedi link precedente).
Quindi è sicuramente un bug C2
, anche se non sono assolutamente sicuro che il fatto che stia tornando alla compilazione di Livello 3 lo influenzi (e perché sta tornando al livello 3, tante incertezze ancora).
È possibile generare il codice dell'assieme con la seguente riga per approfondire ulteriormente la tana del coniglio (vedere anche questo per abilitare la stampa dell'assieme).
java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm
A questo punto sto iniziando a rimanere senza abilità, il comportamento buggy inizia a manifestarsi quando le precedenti versioni compilate vengono scartate, ma quali piccole abilità di assemblaggio ho degli anni '90, quindi lascerò che qualcuno più intelligente di me lo prenda da qui.
È probabile che ci sia già una segnalazione di bug al riguardo, poiché il codice è stato presentato all'OP da qualcun altro e poiché tutto il codice C2 non è privo di bug . Spero che questa analisi sia stata tanto istruttiva per gli altri quanto lo è stata per me.
Come ha sottolineato il venerabile apangin nei commenti, si tratta di un bug recente . Molto obbligato a tutte le persone interessate e disponibili :)