Что на самом деле вызывает ошибку переполнения стека? [дубликат]

этот вопрос уже есть ответ здесь:

  • что такое StackOverflowError? 13 ответов

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

кинули когда переполнение стека происходит из-за слишком глубокой рекурсии приложения.

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

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

подробнее о втором вопросе:

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

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

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

10 ответов


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

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

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

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

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


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

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

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


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

Вызов принят :) 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's 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.... Этот скрипт может писать, компилировать и запускать код о нас:

#!/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 (дан ЯБ Nizet в комментарии) JVM должен бросить StackOverflowError, не умирают:

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


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

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

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

мы хотим создать новый


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

например:

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

В Java:

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

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

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


нет никакого "StackOverFlowException". Вы имеете в виду"StackOverFlowError".

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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


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

private double hours;

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

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

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


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

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

  2. ответ на второй

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

поэтому, рассматривая стек в потоке, мы можем сделать вывод.

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

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