Что на самом деле вызывает ошибку?

Я везде искал и не могу найти надежный ответ. Согласно документации, Java выдает ошибку java.lang.StackOverflowError при следующих обстоятельствах:

Брошено, когда происходит переполнение стека, потому что приложение слишком сильно перегружается.

Но это вызывает два вопроса:

  • Не существуют ли другие способы, а не только через рекурсию?
  • Возникает ли StackOverflowError до того, как JVM фактически переполнит стек или после?

Чтобы уточнить второй вопрос:

Когда Java бросает StackOverflowError, можете ли вы спокойно предположить, что стек не записывался в кучу? Если вы уменьшите размер стека или кучи в попытке/уловить функцию, которая бросает переполнение стека, можете ли вы продолжить работу? Является ли это документированным где угодно?

Ответы, которые не ищу:

  • StackOverflow происходит из-за плохой рекурсии.
  • StackOverflow происходит, когда куча соответствует стеку.

Ответ 1

Кажется, вы думаете, что ошибка stackoverflow подобна исключению переполнения буфера в собственных программах, когда существует риск записи в памяти, которая не была выделена для буфера, и, таким образом, повредить некоторые другие ячейки памяти. Это не так.

JVM имеет заданную память, выделенную для каждого стека каждого потока, и если попытка вызвать метод, чтобы заполнить эту память, JVM выдает ошибку. Точно так же, как если бы вы пытались написать в индексе N массива длины N. Не может произойти повреждение памяти. Стек не может записываться в кучу.

A StackOverflowError для стека, что OutOfMemoryError для кучи: он просто сигнализирует о том, что свободной памяти больше нет.

Описание из ошибок виртуальной машины (§6.3)

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

Ответ 2

Не существуют ли другие способы, а не только через рекурсию?

Конечно. Просто продолжайте называть методы, не возвращаясь. Однако вам понадобится много методов, если вы не разрешите рекурсию. Фактически, это не имеет значения: фрейм стека представляет собой стек стека, независимо от того, является ли он одним из рекурсивного метода или нет. То же самое.

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

Ответ 3

Не существуют ли другие способы, а не только через рекурсию?

Вызов принят:) StackOverflowError без рекурсии (вызов не удался, см. комментарии):

public class Test
{
    final static int CALLS = 710;

    public static void main(String[] args)
    {
        final Functor[] functors = new Functor[CALLS];
        for (int i = 0; i < CALLS; i++)
        {
            final int finalInt = i;
            functors[i] = new Functor()
            {
                @Override
                public void fun()
                {
                    System.out.print(finalInt + " ");
                    if (finalInt != CALLS - 1)
                    {
                        functors[finalInt + 1].fun();
                    }
                }
            };
        }
        // Let get ready to ruuuuuuumble!
        functors[0].fun(); // Sorry, couldn't resist to not comment in such moment. 
    }

    interface Functor
    {
        void fun();
    }
}

Скомпилируйте со стандартным javac Test.java и запустите с помощью java -Xss104k Test 2> out. После этого more out скажет вам:

Exception in thread "main" java.lang.StackOverflowError

Вторая попытка.

Теперь идея еще проще. Примитивы в Java могут храниться в стеке. Итак, пусть объявляет много парных, например double a1,a2,a3.... Этот script может писать, компилировать и запускать код для нас:

#!/bin/sh

VARIABLES=4000
NAME=Test
FILE=$NAME.java
SOURCE="public class $NAME{public static void main(String[] args){double "
for i in $(seq 1 $VARIABLES);
do
    SOURCE=$SOURCE"a$i,"
done
SOURCE=$SOURCE"b=0;System.out.println(b);}}"
echo $SOURCE > $FILE
javac $FILE
java -Xss104k $NAME

И... у меня что-то неожиданное:

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007f4822f9d501, pid=4988, tid=139947823249152
#
# JRE version: 6.0_27-b27
# Java VM: OpenJDK 64-Bit Server VM (20.0-b12 mixed mode linux-amd64 compressed oops)
# Derivative: IcedTea6 1.12.6
# Distribution: Ubuntu 10.04.1 LTS, package 6b27-1.12.6-1ubuntu0.10.04.2
# Problematic frame:
# V  [libjvm.so+0x4ce501]  JavaThread::last_frame()+0xa1
#
# An error report file with more information is saved as:
# /home/adam/Desktop/test/hs_err_pid4988.log
#
# If you would like to submit a bug report, please include
# instructions how to reproduce the bug and visit:
#   https://bugs.launchpad.net/ubuntu/+source/openjdk-6/
#
Aborted

Это 100% повторяющийся. Это связано с вашим вторым вопросом:

Возникает ли StackOverflowError до того, как JVM фактически переполнится стек или после?

Итак, в случае OpenJDK 20.0-b12 мы можем видеть, что JVM сначала взорвался. Но это похоже на ошибку, может быть, кто-то может подтвердить это в комментариях, потому что я не уверен. Должен ли я сообщить об этом? Возможно, это уже исправлено в какой-то новой версии... Согласно ссылка спецификации JVM (данная JB Nizet в комментарии) JVM должен выбросить StackOverflowError, а не умереть:

Если для вычисления в потоке требуется большая виртуальная машина Java стек, чем разрешено, виртуальная машина Java бросает StackOverflowError.


Третья попытка.

public class Test {
    Test test = new Test();

    public static void main(String[] args) {
        new Test();
    }
}

Мы хотим создать новый объект Test. Таким образом, его (неявный) конструктор будет вызван. Но перед этим все элементы Test инициализируются. Итак, Test test = new Test() выполняется сначала...

Мы хотим создать новый объект Test...

Обновление: неудача, это рекурсия, я задал вопрос об этом здесь.

Ответ 4

Наиболее распространенной причиной StackOverFlowError является чрезмерно глубокая или бесконечная рекурсия.

Например:

public int yourMethod(){
       yourMethod();//infinite recursion
}

В Java:

Есть области two в памяти кучи и стека. stack memory используется для хранения локальных переменных и вызова функции, а heap memory используется для хранения объектов в Java.

Если в стеке нет памяти для хранения вызова функции или локальной переменной, JVM будет бросать java.lang.StackOverFlowError

тогда как если для создания объекта больше пустое место, JVM будет бросать java.lang.OutOfMemoryError

Ответ 5

Нет "StackOverFlowException". Вы имеете в виду "StackOverFlowError".

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

Когда именно вызывается ошибка? - Когда вы вызываете метод, и JVM проверяет, достаточно ли памяти для этого. Конечно, ошибка возникает, если это невозможно.

  • Нет, это единственный способ получить эту ошибку: получить полный стек. Но не только через рекурсию, но и вызывающие методы, которые бесконечно называют другие методы. Это очень специфическая ошибка, поэтому нет.
  • Он бросается до того, как стек заполнен, точно, когда вы его проверите. Где бы вы поместили данные, если нет свободного места? Переопределение других? Неее.

Ответ 6

Есть два основных места, которые могут храниться на Java. Первая - это куча, используемая для динамически выделенных объектов. new.

Кроме того, каждый запущенный поток получает свой собственный стек, и он получает объем памяти, выделенный этому стеку.

Когда вы вызываете метод, тогда данные помещаются в стек для записи вызова метода, передаваемых параметров и любых локальных переменных. Метод с пятью локальными переменными и тремя параметрами будет использовать больше пространства стека, чем метод void doStuff() без локальных переменных.

Основными преимуществами стека являются отсутствие фрагментации памяти, все для одного вызова метода назначается в верхней части стека, и что возвращение из методов очень просто. Чтобы вернуться из метода, просто отпустите стек назад к предыдущему методу, установите любое значение, необходимое для возвращаемого значения, и вы закончите.

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

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

Ответ 7

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

Теперь другие вещи, которые должны произойти с StackOverflowError, - это продолжать методы вызова из методов до тех пор, пока вы не получите StackOverflowError, но никто не может запрограммировать получение StackOverflowError, и даже если этот программист делает это, тогда они не следуют стандартам кодирования для cyclomatic complixity, которые каждый программист должен понимать во время программирования. Такая причина для "StackOverflowError" потребует много времени для ее исправления.

Но неосознанно кодирование одной строки или двух строк, которая вызывает StackOverflowError, понятна, и JVM бросает это, и мы можем исправить ее мгновенно. Здесь - это мой ответ с изображением для другого вопроса.

Ответ 8

StackOverflow происходит при вызове функции и заполнении стека.

Как и ArrayOutOfBoundException. Он ничего не может повредить, на самом деле его очень легко поймать и оправиться от него.

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

Ответ 9

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

private double hours;

public double Hours
        {
            get { return Hours; }
            set { Hours = value; }
        }

Как вы можете видеть, это навсегда вернет часы с верхним регистром H, который сам по себе вернет часы и т.д. и т.д.

Переполнение стека часто происходит также из-за нехватки памяти или при использовании управляемых языков, потому что ваш языковой менеджер (CLR, JRE) обнаружит, что ваш код застрял в бесконечном цикле.

Ответ 10

Но это вызывает два вопроса:

  • Не существуют ли другие способы, а не только через рекурсию?
  • Возникает ли StackOverflowError до того, как JVM фактически переполнит стек или после?
  • Он также может возникать, когда мы выделяем размер, превышающий размер стека (например, int x[10000000];).

  • Ответ на второй

Каждый поток имеет свой собственный стек, который содержит кадр для каждого метода, выполняющегося в этом потоке. Таким образом, текущий исполняемый метод находится в верхней части стека. Новый кадр создается и добавляется (помещается) в начало стека для каждого вызова метода. Кадр удаляется (выставляется), когда метод возвращается в обычном режиме, или если во время вызова метода возникает неперехваченное исключение. Стек не управляется напрямую, за исключением объектов push и pop frame, поэтому объекты кадра могут быть выделены в куче, и память не должна быть смежной.

Таким образом, рассматривая стек в потоке, мы можем заключить.

Стек может быть динамическим или фиксированным. Если для потока требуется больший стек, чем разрешен сброс StackOverflowError. Если для потока требуется новый фрейм, и для его выделения недостаточно памяти, то бросается OutOfMemoryError.

вы можете получить описание для JVM здесь