Dies kann zuverlässig (oder nicht reproduziert, je nachdem, was Sie wollen) mit openjdk version "1.8.0_222"
(in meiner Analyse verwendet), OpenJDK 12.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):
--- 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
Richtiger Lauf (keine Anzeige):
--- 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)
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 mal test()
kompiliert (oder dekompiliert) .
Wenn Sie mit -XX:-TieredCompilation
(was entweder interpretiert oder verwendet C2
) 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 der C2
Compiler scheint der Schuldige sein. Dies wird durch Ausführen mit bestätigt -XX:TieredStopAtLevel=1
, wodurch die C2
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).
java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm
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 :)