Понимание стека Java

Есть этот код:

public class Main {
    public static void main(final String[] args) throws Exception {
        System.out.print("1");
        doAnything();
        System.out.println("2");
    }

    private static void doAnything() {
        try {
            doAnything();
        } catch (final Error e) {
            System.out.print("y");
        }
    }
}

И есть вывод:

1yyyyyyyy2

Почему он печатает "y" восемь раз и не более. Как Java может вызвать println(), когда встречается StackOverflowError?

Ответ 1

Здесь вы ловите Error, а не Exception, и в этом случае ваша программа потерпела бы крах.

Если вы попробуете этот код (модифицированный для добавления статического счетчика)

public class StackError {

static int i = 1;

public static void main(final String[] args) throws Exception {
    System.out.print("1");
    doAnything();
    System.out.println("2");
}

private static void doAnything() {
    try {
        i++;
//          System.out.println(i);
        doAnything();
    } catch (Error e) {
        System.out.print("y"+i+"-");

    }
}
}

Выход

 1y6869-2

Итак, у него есть stackerror 6869 раз (изменения для разных прогонов), и последнее значение печатается. Если вы просто напечатаете y, как вы это делали ранее, это может привести к тому, что вывод будет буферизирован и не будет очищен, поскольку это не println.


Update

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

Возвращаясь к этому сценарию, это зависит от внутренней динамики того, насколько заполнен стек и сколько отчетов печати удалось выполнить из catch в doAnything(), и эти числа символов были записаны в буфер, В основной задней части он в конце концов печатается с номером 2.

ссылка javadoc на буферизованные потоки

Ответ 2

Моя ставка заключается в том, что, вызывая print в блоке catch, вы заставляете другой StackOverflowError, который попадает в внешний блок. Некоторые из этих вызовов не будут иметь достаточного количества стека для фактической записи выходного потока.

JLS говорит, что:

Обратите внимание, что StackOverflowError может быть выбрано синхронно по методу вызов, а также асинхронно из-за выполнения собственного метода или ограничения ресурсов виртуальной машины Java.

Платформа Java SE допускает небольшой, но ограниченный объем выполнения произойти до того, как будет создано асинхронное исключение.

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

Ответ 3

При первом вызове StackOverFlowError вызов последнего doAnything() отменяется, и элемент управления возвращается в блок catch из последнего doAnything().

Однако, поскольку стек все еще практически заполнен, простой факт вызова System.out.print("y") вызывает другой StackOverFlowError из-за необходимости нажатия некоторого значения в стеке и последующего вызова функции print().

Следовательно, снова появляется еще один StackOverFlowError, и возврат теперь возвращается в блоке catch {} предыдущего doAnything(); где другой StackOverFlowError будет происходить, потому что необходимость пространства стека, необходимого для выполнения одного вызова System.out.println("y"), больше, чем объем пространства, освобожденного от возврата вызова из doAnything().

Только когда в стеке будет достаточно места для выполнения вызова System.out.print("y"), этот процесс остановится, и блок catch будет успешно завершен. Мы видим, что, выполнив следующую часть кода:

public class Principal3b {

    static int a = 0;
    static int i = 0;
    static int j = 0;

    public static void main(String[] args) {
      System.out.println("X");
        doAnything();
      System.out.println("Y");
        System.out.println(i);        
        System.out.println(j);
    }

    private static void doAnything() {

        a++;
        int b = a;

        try {
            doAnything();
        } catch (final Error e) {
            i++;
            System.out.println(a);
            System.out.println(b);
            j++;
        }
    }
}

Обратите внимание, что вместо print(a) используется println(a); поэтому после каждого значения a следует напечатать новую строку, если все работает нормально.

Однако, когда я запускаю его, я получаю следующий результат:

X
62066206620662066206620662066206
6190
Y
17
1

Это означает, что было выполнено 17 попыток ro, которыми управляет блок catch. Из этих блоков блоков catch 9 не могут ничего печатать, прежде чем генерировать StackOverflowError; 7 могут печатать значение 6190, но не могут распечатать новую строку после нее, перед тем как сами снова запустили ошибку, и, наконец, есть одна, которая может одновременно распечатать значение 6190 и новую строку после нее; поэтому, наконец, разрешив свой блок catch без какого-либо нового StackOverflowError и изящно вернусь к стеку вызовов.

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

Ответ 4

Ясно одно: System.out.print( "y" ); в уловке создает эту головоломку. Если мы изменим код как

static int n;

public static void main(final String[] args) throws Exception {
    System.out.println("1");
    doAnything();
    System.out.println(n);
}

private static void doAnything() {
    try {
        doAnything();
    } catch (Error e) {
        n++;
    }
}

он печатает

1
1

Ответ 5

Ну, нет. раз, когда ошибка попадает, это undefined. Однако JVM позволяет вам восстановить от StackOverflowError ошибку и продолжить выполнение системы в обычном режиме.

Это доказывается следующим кодом:

public class Really {
   public static void main(final String[] args) throws Exception {
     System.out.print("1");
     doAnything();
     System.out.println("2");
   }
   private static void doAnything() {
    try {
           throw new StackOverflowError();
       //doAnything();
    }
    catch(final Error e){
        System.out.print("y");
    }
   }
}

Обратите внимание, однако, как сказал @Javier, StackOverflowError генерируется JVM синхронно или асинхронно (что означает, что он может быть выброшен другим потоком, возможно, родным потоком), поэтому невозможно получить стек след ошибки. Нет. раз поток (ы), попадающий в блок catch(), равен undefined.

Ответ 6

Кроме того, объекты типа Error не являются объектами Exceptions, они представляют исключительные условия. Errors представляют собой необычные ситуации, которые не вызваны программными ошибками, обычно обычно не происходит во время выполнения программы, например, из-за нехватки памяти JVM. Хотя они имеют общий суперкласс Throwable, что означает, что оба могут быть выброшены, он может быть помещены в catch, но обычно не должны быть пойманы, поскольку они представляют редкие, труднодоступные исключительные условия.

Ответ 7

Переполнение стека.

Вы только печатаете на исключение, тем временем программа перегружается в переполнение.

В какой момент это происходит, зависит от отдельных систем, памяти и т.д.

Какова цель программы?