Подтвердить что ты не робот

Путаница вывода из бесконечной рекурсии в try-catch

Рассмотрим следующий код.

public class Action {
private static int i=1;
public static void main(String[] args) {
    try{
        System.out.println(i);
        i++;
        main(args);
    }catch (StackOverflowError e){
        System.out.println(i);
        i++;
        main(args);
    }
 }

}

Я правильно оцениваю значение 4338. После того как вы поймаете вывод StackOverflowError, который будет подключен следующим образом.

4336
4337
4338 // up to this point out put can understand 
433943394339 // 4339 repeating thrice  
434043404340
4341
434243424342
434343434343
4344
4345
434643464346
434743474347
4348
434943494349
435043504350

Рассмотрим Live здесь. Он работает правильно до i=4330. На самом деле, как это происходит?

FYI:

Я сделал следующий код, чтобы понять, что здесь происходит.

public class Action {

    private static int i = 1;
    private static BufferedWriter bw;

    static {
        try {
            bw = new BufferedWriter(new FileWriter("D:\\sample.txt"));
        } catch (IOException e) {
           e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        bw.append(String.valueOf(i)+" ");
        try {
            i++;
            main(args);
        } catch (StackOverflowError e) {
            bw.append(String.valueOf(i)+" ");
            i++;
            main(args);
        }
    }

}

Теперь предыдущая проблема не существует. теперь значение i до 16824744 исправлено и продолжается. Я уверен, что это может привести к значению i=2,147,483,647 (максимальное значение int) без проблем.

Есть проблема с println(). Аналогичные ответы также приведены ниже. Но почему?

Какова будет фактическая причина?

4b9b3361

Ответ 1

Обратите внимание на отсутствие символов новой строки в 433943394339. Это указывает на то, что внутри System.out.println() происходит что-то неправильное.

Существенным моментом здесь является то, что System.out.println() требует, чтобы некоторое пространство стека работало, так что StackOverflowError выбрано из System.out.println().

Вот ваш код с отмеченными точками:

public static void main(String[] args) {
    try{
        System.out.println(i); // (1)
        i++;
        main(args); // (2)
    }catch (StackOverflowError e){
        System.out.println(i); // (3)
        i++;
        main(args); // (4)
    }
}

Представьте себе, что происходит на уровне N рекурсии, когда я = 4338:

  • Заявление (1) на уровне N выводит 4338. Вывод 4338\n
  • i увеличивается до 4339
  • Управляющий поток поступает на уровень N + 1 в (2)
  • Заявление (1) на уровне N + 1 пытается напечатать 4339, но System.out.println() выбрасывает StackOverflowError перед печатью новой строки. Выход 4339
  • StackOverflowError попадает на уровень N + 1, оператор (3) пытается распечатать 4339 и снова сбой по той же причине. Выход 4339
  • Исключение поймано на уровне N. В этот момент доступно больше пространства стека, поэтому оператор (3) пытается напечатать 4339 и успешно (новая строка напечатана правильно). Вывод 4339\n
  • i увеличивается, и поток управления снова переходит на уровень N + 1 в (4)

После этого момента ситуация повторяется с помощью 4340.

Я не уверен, почему некоторые числа печатаются в соответствии с последовательностями без символов новой строки, возможно, это связано с внутренней работой System.out.println() и используемыми буферами.

Ответ 2

Я подозреваю, что это происходит:

  • Печать i
  • Печать новой строки
  • Увеличить i
  • Введите главное
  • Печать i
  • Печать новой строки
  • Увеличить i
  • Введите главное
  • Печать i
  • StackOverflow получил бросок (вместо печати новой строки)
  • Возвратитесь к главному, теперь в catch
  • Печать i
  • StackOverflow снова был сброшен (вместо печати новой строки)
  • Вернитесь к основному, в другое тело catch.
  • Печать i
  • Печать новой строки (теперь больше не работает, потому что мы на два уровня выше)
  • Введите main и вернитесь к 1.

Ответ 3

Согласно моему тесту:

Когда исключение выбрано try block, i имеет то же значение, когда оно входит в блок catch (поскольку его не увеличивают из-за исключения)

а затем внутри блока catch блокируется одно и то же исключение и которое снова захватывается блоком catch!

Я пробовал следующий код

try {
            System.out.println("Try " + i);
            i++;
            main(args);
        } catch (StackOverflowError e) {
            System.out.println("\nBefore");
            System.out.println("Catch " + i);
            i++;
            System.out.println("After");
            main(args);

        }

Выход:

Try 28343
Try 28344
Before
Before
Before
Before
Catch 28344
After
Try 28345
Try 28346
Try 28347
Try 28348
Before
Before
Before
Before
Catch 28348
After
Try 28349

когда try исключает блокировку, он пойман блоком catch, но когда он возвращается к System.out.println("Catch " + i); снова Исключение выбрасывается 4 раза (в моем затмении) Без печати System.out.println("Catch " + i);

Как и в предыдущем выпуске, я проверил его, напечатав текст "Перед", который печатается четыре раза, прежде чем он печатает System.out.println("Catch " + i);

Ответ 4

Если выполнение println (или одного из вызванных им методов) приводит к переполнению стека, вы будете печатать то же самое значение i из предложения catch вложенного main воплощения.

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

Ответ 5

Как уже объясняют другие ответы, это связано с System.out.println, требующим дополнительного места в стеке и тем самым бросая себя в StackOverflowError.

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

public class Action {
  private static int i = 1;

  private static StringBuffer buffer = new StringBuffer();

  public static void main(String[] args) {
    try {
      print();
      main(args);
    }
    catch (StackOverflowError e) {
      print();
      main(args);
    }
  }

  private static void print() {
    buffer.append(i).append("\n");
    i++;
    if (i % 1000 == 0) {
      System.out.println("more: " + buffer);
      buffer = new StringBuffer();
    }
  }
}

Важным уроком для изучения является: Никогда не ломайте Ошибки, поскольку они действительно указывают на то, что с вашей JVM что-то серьезное не получается, что нельзя нормально обрабатывать.

Ответ 6

Суть в том, что StackOverflowError является ошибкой, а не исключением. Вы обрабатываете ошибку, а не исключение. Так что программа уже разбилась, когда она входит в блок catch. Относительно странного поведения программы ниже приведено объяснение на основе java-буферов из этот официальный документ:

Например, объект PrintWriter с автозагрузкой очищает буфер каждый вызов println или формата

System.out.println() внутренне вызывает PrintStream, который буферизуется. Вы не теряете данные из буфера, все записывается на вывод (терминал в вашем случае) после того, как он заполняется, или когда вы явно вызываете флеш на нем.

Возвращаясь к этому сценарию, это зависит от внутренней динамики того, насколько заполнен стек и сколько отчетов печати удалось выполнить из catch в main(), и эти числа символов были записаны в буфер, Здесь после выполнения первой попытки, то есть в случае первого появления, первый System.out.println() не может напечатать новую строку, чтобы он сбросил буфер с оставшимися символами.

Ответ 7

axtavt Answer is very complete, но я хотел бы добавить это:

Как вы знаете, стек используется для хранения памяти переменных, на основе чего вы не можете создавать новые переменные, когда вы достигаете предела, верно, что System.out.println потребуется несколько ресурсов стека

787     public void More ...println(Object x) {
788         String s = String.valueOf(x);
789         synchronized (this) {
790             print(s);
791             newLine();
792         }
793     }

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

public class Action {
    static int i = 1;

    public static void main(String[] args) {
        try {
            System.out.print(i + "\n");
            i++;
            main(args);
        } catch (StackOverflowError e) {
            System.out.print(i + " SO " + "\n");
            i++;
            main(args);
        }
    }
}

Теперь вы не будете просить стек обрабатывать новые строки, вы будете использовать константу "\n", и вы можете добавить некоторую отладку в строку печати исключений, и ваш вывод не будет иметь нескольких значений в одной строке:

10553
10553 SO
10553 SO
10554
10554 SO
10554 SO
10555
10556
10557
10558

И он будет продолжать прерываться, пока не будет выделено несколько ресурсов для распределения новых данных и перехода к следующему значению i.