Это может быть надежно воспроизведено (или не воспроизведено, в зависимости от того, что вы хотите) с помощью 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, еще много неопределенностей).
Вы можете сгенерировать код сборки с помощью следующей строки, чтобы еще глубже проникнуть в кроличью нору (см. также this , чтобы включить печать сборки).
java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm
В этот момент у меня заканчиваются навыки, глючное поведение начинает проявляться, когда предыдущие скомпилированные версии отбрасываются, но какая маленькая сборка лыжиlls у меня из 90-х, так что я позволю кому-нибудь умнее меня взять его отсюда.
Вполне вероятно, что об этом уже есть сообщение об ошибке, так как код был предоставлен ОП кому-тоиначе, и как весь код C2 не без ошибок . Я надеюсь, что этот анализ был таким же информативным для других, как и для меня.
Как отметил почтенный апангин в комментариях, это недавняя ошибка . Большое спасибо всем заинтересованным и полезным людям:)