Что такое StackOverflowError? - PullRequest
       84

Что такое StackOverflowError?

391 голосов
/ 18 октября 2008

Что такое StackOverflowError, что его вызывает и как с ними бороться?

Ответы [ 13 ]

363 голосов
/ 18 октября 2008

Параметры и локальные переменные размещаются в стеке (со ссылочными типами объект находится в куче 1004 *, а переменная в стеке ссылается на этот объект в куче). Стек обычно находится в верхнем конце вашего адресного пространства и по мере его использования он направляется к нижнему адресного пространства (то есть к нулю).

У вашего процесса также есть куча , которая находится в нижнем конце вашего процесса. По мере выделения памяти эта куча может увеличиваться в направлении верхнего края вашего адресного пространства. Как видите, существует вероятность, что куча "столкнется" со стеком (немного похоже на тектонические плиты !!!).

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

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

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

Если у вас нет явных рекурсивных функций, проверьте, вызываете ли вы какие-либо библиотечные функции, которые косвенно вызовут вашу функцию (как неявный случай выше).

86 голосов
/ 26 марта 2015

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

Локальные переменные хранятся в стеке : enter image description here

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

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

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

Пример броска StackOverflowError показан ниже:

StackOverflowErrorExample.java:

public class StackOverflowErrorExample {

    public static void recursivePrint(int num) {
        System.out.println("Number: " + num);

        if(num == 0)
            return;
        else
            recursivePrint(++num);
    }

    public static void main(String[] args) {
        StackOverflowErrorExample.recursivePrint(1);
    }
}

В этом примере мы определяем рекурсивный метод, называемый recursivePrint, который печатает целое число, а затем вызывает сам себя со следующим последующим целым числом в качестве аргумента. Рекурсия заканчивается, пока мы не передадим 0 в качестве параметра. Однако в нашем примере мы передали параметр от 1 и его растущих последователей, поэтому рекурсия никогда не прекратится.

Пример выполнения с использованием флага -Xss1M, который задает размер стека потока равным 1 МБ, показан ниже:

Number: 1
Number: 2
Number: 3
...
Number: 6262
Number: 6263
Number: 6264
Number: 6265
Number: 6266
Exception in thread "main" java.lang.StackOverflowError
        at java.io.PrintStream.write(PrintStream.java:480)
        at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
        at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
        at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
        at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
        at java.io.PrintStream.write(PrintStream.java:527)
        at java.io.PrintStream.print(PrintStream.java:669)
        at java.io.PrintStream.println(PrintStream.java:806)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:4)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
        at StackOverflowErrorExample.recursivePrint(StackOverflowErrorExample.java:9)
        ...

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

Как работать с ошибкой StackOverflowError

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

  2. Если вы убедились, что рекурсия реализован правильно, вы можете увеличить размер стека, в Чтобы разрешить большее количество вызовов. В зависимости от Java Виртуальная машина (JVM) установлена, размер стека потока по умолчанию может равно либо 512 КБ, либо 1 МБ . Вы можете увеличить стек потоков размер с использованием флага -Xss. Этот флаг может быть указан либо через Конфигурация проекта или через командную строку. Формат -Xss Аргумент: -Xss<size>[g|G|m|M|k|K]

62 голосов
/ 18 октября 2008

Если у вас есть такая функция, как:

int foo()
{
    // more stuff
    foo();
}

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

23 голосов
/ 18 октября 2008

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

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

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

Таким образом, переполнение стека появляется там, где вы слишком много выделяете в стеке. Например, в упомянутой рекурсии.

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

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

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

8 голосов
/ 18 октября 2008

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

6 голосов
/ 18 октября 2008

Как вы говорите, вам нужно показать код. : -)

Ошибка переполнения стека обычно возникает, когда ваша функция вызывает гнездо слишком глубоко. См. Ветку Stack Overflow Code Golf с некоторыми примерами того, как это происходит (хотя в случае этого вопроса ответы намеренно вызывают переполнение стека).

5 голосов
/ 18 июля 2012

StackOverflowError в стек, как OutOfMemoryError в кучу.

Неограниченные рекурсивные вызовы приводят к использованию стекового пространства.

В следующем примере получается StackOverflowError:

class  StackOverflowDemo
{
    public static void unboundedRecursiveCall() {
     unboundedRecursiveCall();
    }

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

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

5 голосов
/ 18 октября 2008

Наиболее распространенной причиной переполнения стека является чрезмерно глубокая или бесконечная рекурсия . Если это ваша проблема, это руководство по Java Recursion может помочь понять проблему.

3 голосов
/ 30 марта 2017

A StackOverflowError - это ошибка времени выполнения в Java.

Выдается при превышении объема памяти стека вызовов, выделенного JVM.

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

Пример:

public class Factorial {
    public static int factorial(int n){
        if(n == 1){
            return 1;
        }
        else{
            return n * factorial(n-1);
        }
    }

    public static void main(String[] args){
        System.out.println("Main method started");
        int result = Factorial.factorial(-1);
        System.out.println("Factorial ==>"+result);
        System.out.println("Main method ended");
    }
}

Трассировка стека:

Main method started
Exception in thread "main" java.lang.StackOverflowError
at com.program.stackoverflow.Factorial.factorial(Factorial.java:9)
at com.program.stackoverflow.Factorial.factorial(Factorial.java:9)
at com.program.stackoverflow.Factorial.factorial(Factorial.java:9)

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

3 голосов
/ 16 января 2013

Вот пример рекурсивного алгоритма обращения к односвязному списку. На ноутбуке со следующими характеристиками (память 4G, процессор Intel Core i5 2,3 ГГц, 64-разрядная ОС Windows 7) эта функция будет приводить к ошибке StackOverflow для связанного списка размером около 10 000.

Я хочу сказать, что мы должны разумно использовать рекурсию, всегда принимая во внимание масштаб системы. Часто рекурсию можно преобразовать в итеративную программу, которая лучше масштабируется. (Одна итерационная версия того же алгоритма приведена внизу страницы, она переворачивает односвязный список размером 1 миллион за 9 миллисекунд.)

    private static LinkedListNode doReverseRecursively(LinkedListNode x, LinkedListNode first){

    LinkedListNode second = first.next;

    first.next = x;

    if(second != null){
        return doReverseRecursively(first, second);
    }else{
        return first;
    }
}

public static LinkedListNode reverseRecursively(LinkedListNode head){
    return doReverseRecursively(null, head);
}

Итерационная версия того же алгоритма:

    public static LinkedListNode reverseIteratively(LinkedListNode head){
    return doReverseIteratively(null, head);
}   

private static LinkedListNode doReverseIteratively(LinkedListNode x, LinkedListNode first) {

    while (first != null) {
        LinkedListNode second = first.next;
        first.next = x;
        x = first;

        if (second == null) {
            break;
        } else {
            first = second;
        }
    }
    return first;
}


public static LinkedListNode reverseIteratively(LinkedListNode head){
    return doReverseIteratively(null, head);
}
...