Це можна надійно відтворити (або не відтворити, залежно від того, що ви хочете) за допомогою 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-х, тому я дозволю комусь розумнішим за мене звідси.
Ймовірно, що про це вже є звіт про помилку, оскільки код був представлений ОП кимось іншим, і як і весь код С2, не має помилок . Я сподіваюся, що цей аналіз був таким же інформативним для інших, як і для мене.
Як в коментарях вказав поважний апангін, це нещодавня помилка . Дуже зобов’язаний усім зацікавленим та корисним людям :)