Überraschenderweise wird der folgende Code ausgegeben:
/
-1
Der Code:
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);
}
}
}
Ich habe viele Male versucht festzustellen, wie oft dies auftreten würde, aber leider war es letztendlich ungewiss, und ich stellte fest, dass die Ausgabe von -2 manchmal zu einer Periode wurde. Außerdem habe ich versucht, die while-Schleife zu entfernen und -1 ohne Probleme auszugeben. Wer kann mir sagen warum?
Informationen zur JDK-Version:
HopSpot 64-Bit 1.8.0.171
IDEA 2019.1.1
Antworten:
Dies kann zuverlässig (oder nicht reproduziert, je nachdem, was Sie wollen) mit
openjdk version "1.8.0_222"
(in meiner Analyse verwendet), OpenJDK12.0.1
(nach Oleksandr Pyrohov) und OpenJDK 13 (nach Carlos Heuberger) reproduziert werden.Ich habe den Code mit
-XX:+PrintCompilation
genügend Zeit ausgeführt, um beide Verhaltensweisen zu erhalten, und hier sind die Unterschiede.Buggy-Implementierung (zeigt die Ausgabe an):
Richtiger Lauf (keine Anzeige):
Wir können einen signifikanten Unterschied feststellen. Bei korrekter Ausführung kompilieren wir
test()
zweimal. Einmal am Anfang und noch einmal danach (vermutlich, weil die JIT bemerkt, wie heiß die Methode ist). Im Buggy wird die Ausführung 5 maltest()
kompiliert (oder dekompiliert) .Wenn Sie mit
-XX:-TieredCompilation
(was entweder interpretiert oder verwendetC2
) oder mit-Xbatch
(was die Kompilierung zwingt, im Hauptthread statt parallel ausgeführt zu werden) ausgeführt werden, ist die Ausgabe garantiert und mit 30000 Iterationen wird eine Menge Material ausgedruckt, so dass derC2
Compiler scheint der Schuldige sein. Dies wird durch Ausführen mit bestätigt-XX:TieredStopAtLevel=1
, wodurch dieC2
Ausgabe deaktiviert und nicht erzeugt wird (das Anhalten auf Stufe 4 zeigt den Fehler erneut an).Bei der korrekten Ausführung wird die Methode zuerst mit der Kompilierung der Ebene 3 und anschließend mit der Kompilierung der Ebene 4 kompiliert .
Bei der fehlerhaften Ausführung werden die vorherigen Kompilierungen verworfen (
made non entrant
) und erneut auf Ebene 3 kompiliert (C1
siehe vorherigen Link).Es ist also definitiv ein Fehler
C2
, obwohl ich nicht absolut sicher bin, ob die Tatsache, dass es zurück zur Level 3-Kompilierung geht, sich darauf auswirkt (und warum es zurück zu Level 3 geht, so viele Unsicherheiten immer noch).Sie können den Baugruppencode mit der folgenden Zeile generieren, um noch tiefer in das Kaninchenloch zu gelangen (siehe auch dies , um das Drucken von Baugruppen zu ermöglichen).
An diesem Punkt gehen mir langsam die Fähigkeiten aus, das fehlerhafte Verhalten zeigt sich, wenn die vorherigen kompilierten Versionen verworfen werden, aber die wenigen Montagefähigkeiten, die ich habe, stammen aus den 90ern, also lasse ich jemanden schlauer als ich von hier.
Es ist wahrscheinlich, dass es bereits einen Fehlerbericht darüber gibt, da der Code dem OP von jemand anderem präsentiert wurde und da der gesamte Code C2 nicht ohne Fehler ist . Ich hoffe, diese Analyse war für andere genauso informativ wie für mich.
Wie der ehrwürdige Apangin in den Kommentaren hervorhob, ist dies ein neuer Fehler . Vielen Dank an alle interessierten und hilfsbereiten Leute :)
quelle
C2
- habe den generierten Assembler-Code mit JitWatch betrachtet (und versucht, ihn zu verstehen) - derC1
generierte Code ähnelt immer noch dem Bytecode,C2
ist völlig anders (ich konnte nicht einmal die Initialisierung voni
8 finden)Dies ist ehrlich gesagt ziemlich seltsam, da dieser Code technisch nie ausgegeben werden sollte, weil ...
... sollte immer
i
zu-1
(8 - 3 = 5; 5 - 3 = 2; 2 - 3 = -1) führen. Was noch seltsamer ist, ist, dass es niemals im Debug-Modus meiner IDE ausgegeben wird.Interessanterweise ist der Moment, in dem ich vor der Konvertierung einen Scheck hinzufüge
String
, kein Problem ...Nur zwei Punkte guter Codierungspraxis ...
String.valueOf()
.equals()
als das Argument sein sollen, wodurch NullPointerExceptions minimiert werden.Die einzige Möglichkeit, dies nicht zu erreichen, war die Verwendung von
String.format()
... im Grunde sieht es so aus, als ob Java ein bisschen Zeit braucht, um zu Atem zu kommen :)
BEARBEITEN: Dies mag völlig zufällig sein, aber es scheint eine gewisse Übereinstimmung zwischen dem auszudruckenden Wert und der ASCII-Tabelle zu bestehen .
i
=-1
, angezeigtes Zeichen ist/
(ASCII-Dezimalwert von 47)i
=-2
, angezeigtes Zeichen ist.
(ASCII-Dezimalwert von 46)i
=-3
, angezeigtes Zeichen ist-
(ASCII-Dezimalwert von 45)i
=-4
, angezeigtes Zeichen ist,
(ASCII-Dezimalwert von 44)i
=-5
, angezeigtes Zeichen ist+
(ASCII-Dezimalwert von 43)i
=-6
, angezeigtes Zeichen ist*
(ASCII-Dezimalwert von 42)i
=-7
, angezeigtes Zeichen ist)
(ASCII-Dezimalwert von 41)i
=-8
, angezeigtes Zeichen ist(
(ASCII-Dezimalwert von 40)i
=-9
, angezeigtes Zeichen ist'
(ASCII-Dezimalwert von 39)Was wirklich interessant ist, ist, dass das Zeichen bei ASCII-Dezimalzahl 48 der Wert ist
0
und 48 - 1 = 47 (Zeichen/
) usw.quelle
(int)'/' == 47
;(char)-1
ist undefiniert0xFFFF
ist <kein Zeichen> in Unicode)getNumericValue()
mit dem gegebenen Code zusammen? und wie konvertiert es-1
zu'/'
??? Warum nicht'-'
,getNumericValue('-')
ist auch-1
??? (-1
getNumericValue()
aufvalue
(/
), um den Zeichenwert zu erhalten. Sie sind zu 100% richtig, dass der ASCII-Dezimalwert von/
47 sein sollte (es war das, was ich auch erwartet hatte), abergetNumericValue()
zu diesem Zeitpunkt -1 zurückgegeben hat, wie ich hinzugefügt hatteSystem.out.println(Character.getNumericValue(value.toCharArray()[0]));
. Ich kann die Verwirrung sehen, auf die Sie sich beziehen, und habe den Beitrag aktualisiert.Ich weiß nicht, warum Java eine so zufällige Ausgabe liefert, aber das Problem liegt in Ihrer Verkettung, die bei größeren Werten
i
innerhalb derfor
Schleife fehlschlägt .Wenn Sie die
String value = i + "";
Zeile durchString value = String.valueOf(i) ;
Ihren Code ersetzen , funktioniert dies wie erwartet.Die Verkettung
+
zum Konvertieren des int in einen String ist nativ und möglicherweise fehlerhaft (seltsamerweise gründen wir sie jetzt wahrscheinlich) und verursacht ein solches Problem.Hinweis: Ich habe den Wert von i inside for loop auf 10000 reduziert und hatte kein Problem mit der
+
Verkettung.Dieses Problem muss den Java-Stakeholdern gemeldet werden und sie können ihre Meinung dazu abgeben.
Bearbeiten Ich habe den Wert von i in for loop auf 3 Millionen aktualisiert und eine neue Reihe von Fehlern wie folgt gesehen:
Meine Java-Version ist 8.
quelle
StringConcatFactory
(OpenJDK 13) oderStringBuilder
(Java 8)StringConcatFactory
Klasse sein muss. aber soweit ich weiß, Java bis Java 8 Java nicht unterstützen Operator ÜberladungException in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
Fehlermeldung. Seltsam.i + ""
wird genau wienew StringBuilder().append(i).append("").toString()
in Java 8 kompiliert , und wenn Sie dies verwenden, wird schließlich auch die Ausgabe erzeugt