Выход -1 становится косой чертой в цикле

Удивительно, но следующий код выводит:

/
-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
44
задан Laurel 31 October 2019 в 12:09
поделиться

3 ответа

Это может быть надежно воспроизведено (или не воспроизведено, в зависимости от того, что Вы хотите) с openjdk version "1.8.0_222" (используемый в моем анализе), OpenJDK 12.0.1 (по словам Oleksandr Pyrohov) и OpenJDK 13 (по словам Carlos Heuberger).

я выполнил код с -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) или с [1 110] (который вынуждает компиляцию работать в основном потоке, вместо параллельно), вывод , гарантировал , и с 30 000 повторений распечатывает много материала, таким образом, C2 компилятор, кажется, преступник. Это подтверждено путем выполнения с [1 112], который отключает C2 и не производит вывод (останавливающийся на шоу уровня 4 ошибка снова).

В корректном выполнении, метод сначала компилируется с [1 117] компиляция Уровня 3 , затем впоследствии с Уровнем 4.

В ошибочном выполнении, предыдущие компиляции являются discared (made non entrant), и это снова компилируется на Уровне 3 (который является C1, см. предыдущую ссылку).

, Таким образом, это определенно - ошибка в [1 116], хотя я не абсолютно уверен, влияет ли то, что это возвращается к компиляции Уровня 3, на него (и почему это возвращается к уровню 3, столько неуверенности все еще).

можно генерировать ассемблерный код со следующей строкой для движения еще глубже в кроличью нору (также см. это для включения печати блока).

java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm

В этой точке я начинаю заканчиваться навыки, ошибочное поведение начинает показывать, когда предыдущие скомпилированные версии отбрасываются, но что небольшие навыки блока, которые я имею, с 90-х, таким образом, я позволю кому-то более умному, чем я, берут его отсюда.

вероятно, что уже существует отчет об ошибках об этом, так как код был представлен OP кем-то еще, и как весь код , C2 не без ошибок . Я надеюсь, что этот анализ был так же информативен другим, как это было мне.

Как почтенный apangin, на который указывают в комментариях, это недавняя ошибка . Очень обязанный всем заинтересованным и услужливым людям :)

30
ответ дан 11 November 2019 в 22:33
поделиться

Не знайте, почему Java дает такой случайный вывод, но проблема находится в Вашей конкатенации, которая перестала работать для больших значений i внутренняя часть for цикл.

, Если Вы заменяете String value = i + ""; строка String value = String.valueOf(i) ; Ваши работы кода как ожидалось.

Конкатенация с помощью + для преобразования интервала для строкового представления является собственной и могла бы быть багги (Странно, мы основываем его теперь, вероятно) и вызываем такую проблему.

Примечание: Я уменьшил значение меня внутри для цикла к 10 000, и я не столкнулся с проблемой с + конкатенация.

об Этой проблеме нужно сообщить заинтересованным сторонам Java & они могут дать свое мнение о том же.

Редактирование я обновил значение меня в для цикла к 3 миллионам и видел новый набор ошибок как указано ниже:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
    at java.lang.Integer.getChars(Integer.java:463)
    at java.lang.Integer.toString(Integer.java:402)
    at java.lang.String.valueOf(String.java:3099)
    at solving.LoopOutPut.test(LoopOutPut.java:16)
    at solving.LoopOutPut.main(LoopOutPut.java:8)

Моя версия Java равняется 8.

0
ответ дан 11 November 2019 в 22:33
поделиться

Это честно довольно нечетно, поскольку тот код никогда не должен технически производить потому что...

int i = 8;
while ((i -= 3) > 0);

... должен всегда приводить к i являющийся -1 (8 - 3 = 5; 5 - 3 = 2; 2 - 3 =-1). То, что является еще более странным, то, что это никогда выводы в режиме отладки моего IDE.

Интересно, момент я добавляю проверку перед преобразованием в String, затем без проблем...

public void test() {
  int i = 8;
  while ((i -= 3) > 0);
  if(i != -1) { System.out.println("Not -1"); }
  String value = String.valueOf(i);
  if (!"-1".equalsIgnoreCase(value)) {
    System.out.println(value);
    System.out.println(i);
  }
}

Всего две точки хорошей практики кодирования...

  1. Скорее использование String.valueOf()
  2. Некоторые стандарты кодирования указывают, что Строковые литералы должны быть целью .equals(), а не аргумент, таким образом минимизировав NullPointerExceptions.

единственный способ, которым я заставил это не происходить, был при помощи String.format()

public void test() {
  int i = 8;
  while ((i -= 3) > 0);
  String value = String.format("%d", i);
  if (!"-1".equalsIgnoreCase(value)) {
    System.out.println(value);
    System.out.println(i);
  }
}

... по существу просто похоже, что 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 (символ /), и т.д.

3
ответ дан 11 November 2019 в 22:33
поделиться