Удивительно, но следующий код выводит:
/
-1
Код:
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);
}
}
}
Я пытался много раз определить, сколько раз это произойдет, но, к сожалению, это было в конечном итоге неопределенным, и я обнаружил, что выходное значение -2 иногда превращается в период. Кроме того, я также попытался удалить цикл while и вывести -1 без проблем. Кто может сказать мне, почему?
Информация о версии JDK:
HopSpot 64-Bit 1.8.0.171
IDEA 2019.1.1
Ответы:
Это может быть надежно воспроизведено (или не воспроизведено, в зависимости от того, что вы хотите) с
openjdk version "1.8.0_222"
(используется в моем анализе), OpenJDK12.0.1
(по мнению Александра Пирогова) и OpenJDK 13 (по Карлосу Хойбергеру).Я запускал код с
-XX:+PrintCompilation
достаточным количеством раз, чтобы получить оба поведения, и вот различия.Глючная реализация (отображает вывод):
Правильный прогон (без отображения):
Мы можем заметить одно существенное отличие. При правильном исполнении мы компилируем
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-му уровню, так много неопределенностей все еще).Вы можете сгенерировать код сборки с помощью следующей строки, чтобы еще глубже проникнуть в кроличью нору (см. Также это, чтобы включить печать сборки).
В этот момент у меня заканчиваются навыки, поведение глючного начинает проявляться, когда отбрасываются предыдущие скомпилированные версии, но какие у меня мало навыков сборки из 90-х, так что я позволю кому-нибудь умнее меня взять его отсюда.
Вполне вероятно, что об этом уже есть сообщение об ошибке, так как код был представлен ОП другому, и, как и весь код C2, не без ошибок . Я надеюсь, что этот анализ был таким же информативным для других, как и для меня.
Как отметил в комментариях почтенный апангин, это недавняя ошибка . Большое спасибо всем заинтересованным и полезным людям :)
источник
C2
- посмотрел на сгенерированный ассемблерный код (и попытался понять его) с помощью JitWatch -C1
сгенерированный код все еще напоминает байт-код,C2
он совершенно другой (я даже не смог найти инициализациюi
с 8)Честно говоря, это довольно странно, поскольку этот код технически никогда не должен выводиться, потому что ...
... должен всегда приводить к
i
к-1
(8 - 3 = 5; 5 - 3 = 2; 2 - 3 = -1). Что еще более странно, так это то, что он никогда не выводится в режиме отладки моей IDE.Интересно, что в тот момент, когда я добавляю чек перед преобразованием в a
String
, тогда нет проблем ...Всего два пункта хорошей практики кодирования ...
String.valueOf()
.equals()
, а не аргументом, минимизируя таким образом исключения NullPointerException.Единственный способ, которым я получил это, чтобы не произошло, было с помощью
String.format()
... по сути, похоже, что Java нужно немного времени, чтобы отдышаться :)
РЕДАКТИРОВАТЬ: Это может быть совершенно случайно, но, кажется, существует некоторое соответствие между значением, которое печатается и таблицы ASCII .
i
=-1
, символ отображается/
(десятичное значение ASCII 47)i
=-2
, отображаемый.
символ (десятичное значение ASCII 46)i
=-3
, символ отображается-
(десятичное значение ASCII 45)i
=-4
, отображаемый,
символ (десятичное значение ASCII 44)i
=-5
, отображаемый+
символ (десятичное значение ASCII 43)i
=-6
, отображаемый*
символ (десятичное значение ASCII 42)i
=-7
, символ отображается)
(десятичное значение ASCII 41)i
=-8
, отображаемый(
символ (десятичное значение ASCII 40)i
=-9
, символ отображается'
(десятичное значение ASCII 39)Что действительно интересно, так это то, что символ в ASCII десятичном значении 48 является значением,
0
а 48 - 1 = 47 (символ/
) и т. Д.источник
(int)'/' == 47
;(char)-1
Неопределен0xFFFF
есть <не символ> в Unicode)getNumericValue()
относится к данному коду ??? и как это конвертировать-1
в'/'
??? Почему бы и нет'-'
,getNumericValue('-')
тоже-1
??? (Кстати, многие методы возвращаются-1
)getNumericValue()
наvalue
(/
) , чтобы получить значение символа. Вы на 100% правы, что десятичное значение ASCII/
должно быть 47 (это было то, что я также ожидал), ноgetNumericValue()
возвращало -1 в тот момент, как я добавилSystem.out.println(Character.getNumericValue(value.toCharArray()[0]));
. Я вижу путаницу, на которую вы ссылаетесь, и обновил пост.Не знаю, почему Java дает такой случайный вывод, но проблема в вашей конкатенации, которая не работает для больших значений
i
внутриfor
цикла.Если вы замените
String value = i + "";
строку сString value = String.valueOf(i) ;
вашим кодом работает как положено.Конкатенация, использующаяся
+
для преобразования int в строку, является нативной и может содержать ошибки (как ни странно, мы сейчас ее обнаруживаем, вероятно) и вызывает такую проблему.Примечание: я уменьшил значение i внутри цикла for до 10000, и у меня не возникло проблем с
+
конкатенацией.Об этой проблеме необходимо сообщить заинтересованным сторонам Java, и они могут высказать свое мнение о них.
Изменить Я обновил значение i в для цикла до 3 миллионов и увидел новый набор ошибок, как показано ниже:
Моя версия Java 8.
источник
StringConcatFactory
(OpenJDK 13) илиStringBuilder
(Java 8)StringConcatFactory
класс. но насколько я знаю, ява до ява 8 ява не поддерживает перегрузку оператораException in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
ошибку. Странный.i + ""
компилируется точно так же, какnew StringBuilder().append(i).append("").toString()
в Java 8, и использование этого в конечном итоге также приводит к выводу