Это может быть надежно воспроизведено (или не воспроизведено, в зависимости от того, что вы хотите) с openjdk version "1.8.0_222"
(используется в моем анализе), OpenJDK 12.0.1
(по мнению Александра Пирогова) и OpenJDK 13 (по Карлосу Хойбергеру).
Я запускал код с -XX:+PrintCompilation
достаточным количеством раз, чтобы получить оба поведения, и вот различия.
Глючная реализация (отображает вывод):
--- 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
Правильный прогон (без отображения):
--- 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)
Мы можем заметить одно существенное отличие. При правильном исполнении мы компилируем test()
дважды. Один раз в начале и еще раз потом (предположительно потому, что JIT замечает, насколько горячий метод). В глючном исполнении test()
компилируется (или декомпилируется) 5 раз.
Кроме того, при выполнении с -XX:-TieredCompilation
(который либо интерпретирует, либо использует C2
), либо с -Xbatch
(который заставляет компиляцию запускаться в основном потоке, а не параллельно), вывод гарантирован, а с 30000 итерациями выводится много материала, поэтому C2
компилятор кажется быть виновником. Это подтверждается запуском с -XX:TieredStopAtLevel=1
, который отключает C2
и не производит вывод (остановка на уровне 4 снова показывает ошибку).
В правильном исполнении метод сначала компилируется с компиляцией уровня 3 , а затем с уровнем 4.
В глючном исполнении предыдущие компиляции отбрасываются ( made non entrant
) и снова компилируются на уровне 3 ( C1
см. Предыдущую ссылку).
Так что это определенно ошибка C2
, хотя я не совсем уверен, влияет ли на нее факт, что он возвращается к компиляции 3-го уровня (и почему он возвращается к 3-му уровню, так много неопределенностей все еще).
Вы можете сгенерировать код сборки с помощью следующей строки, чтобы еще глубже проникнуть в кроличью нору (см. Также это, чтобы включить печать сборки).
java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm
В этот момент у меня заканчиваются навыки, поведение глючного начинает проявляться, когда отбрасываются предыдущие скомпилированные версии, но какие у меня мало навыков сборки из 90-х, так что я позволю кому-нибудь умнее меня взять его отсюда.
Вполне вероятно, что об этом уже есть сообщение об ошибке, так как код был представлен ОП другому, и, как и весь код C2, не без ошибок . Я надеюсь, что этот анализ был таким же информативным для других, как и для меня.
Как отметил в комментариях почтенный апангин, это недавняя ошибка . Большое спасибо всем заинтересованным и полезным людям :)